Getting Started
Core Architecture
Link Engine
Analytics & Attribution
Partners & Affiliates
Third-Party Integrations
Identity & Security
Automation & Messaging
Developer Tools
The following files were used as context for generating this wiki page:
Link resolution and redirection form the core traffic-routing engine of Dub, capturing inbound short-link requests at the edge and mapping them to destination URLs, target assets, or administrative fallback pages. This system addresses the challenges of low-latency distributed lookups, case sensitivity, internationalized domain names, and granular tracking across multi-tenant workspaces. Key design decisions include multi-tier caching with Redis and edge databases, on-demand fallback crawls for legacy providers, and flexible URL parameter preservation. By integrating directly with edge middleware, analytics, and partner attribution pipelines, the resolution subsystem ensures fast, secure, and context-aware request handling worldwide. Sources: apps/web/lib/middleware/link.ts:43-122, apps/web/lib/planetscale/get-link-via-edge.ts:10-39
Dub captures and resolves inbound traffic at the network edge using Next.js middleware and edge-optimized database queries. When a request hits the edge, the system first inspects the hostname and path through global routing checks before delegating link resolution to caching layers or directly querying the underlying relational datastore via PlanetScale. Sources: apps/web/middleware.ts:34-89, apps/web/lib/middleware/link.ts:43-104
Incoming requests enter through middleware.ts, which sets up a runtime environment and parses the request context using parse(req). The request is evaluated against known hostnames—such as application dashboards, API endpoints, or administrative portals—before falling through to the short-link resolution pipeline in LinkMiddleware. Sources: apps/web/middleware.ts:20-89, apps/web/lib/middleware/link.ts:43-44
Sources: apps/web/middleware.ts:34-89, apps/web/lib/middleware/link.ts:43-104, apps/web/lib/planetscale/get-link-via-edge.ts:10-68
The edge resolution path executes a precise sequence of functions to fetch link records from the edge database when cache misses occur:
POST (or incoming middleware invocation) receives the initial request payload or URL path. Sources: apps/web/app/ee/api/track/open/route.ts:23-97getLinkViaEdge checks an in-flight lookup map (inFlightLinkLookups) using a composite ${domain}:${key} string to deduplicate concurrent requests for the same short link. Sources: apps/web/lib/planetscale/get-link-via-edge.ts:41-68getLinkViaEdgeHelper normalizes the domain's case sensitivity, applies punycode and URI decoding, and executes a prepared SQL query (SELECT * FROM Link WHERE domain = ? AND \key` = ?`) against the database connection. Sources: apps/web/lib/planetscale/get-link-via-edge.ts:10-39Note
getLinkViaEdge uses an in-memory Map called inFlightLinkLookups to prevent duplicate database queries when multiple concurrent requests arrive for the exact same uncached link, sharing the resulting promise across callers. Sources: apps/web/lib/planetscale/get-link-via-edge.ts:41-68
The edge link resolution layer relies on specialized utility modules to normalize identifiers, handle deduplication, and query persistent storage.
Sources: apps/web/lib/middleware/link.ts:43-122, apps/web/lib/planetscale/get-link-via-edge.ts:10-68, apps/web/app/ee/api/track/open/route.ts:23-110
Sources: apps/web/lib/middleware/link.ts:89-104, apps/web/lib/planetscale/get-link-via-edge.ts:10-68
Domain normalization and key encoding dictate how raw URL path segments and hostnames are transformed into queries suitable for persistent storage and database lookups. Depending on whether a domain is case-sensitive, keys undergo distinct transformation pipelines involving URI decoding, Unicode normalization, and punycode encoding. Sources: apps/web/lib/planetscale/get-link-via-edge.ts:10-39, apps/web/lib/api/links/utils/process-key.ts:8-41
The processKey utility handles validation and sanitization for link keys before they are stored or queried. It evaluates reserved routes, regular expression constraints, and default Dub domain specifics. Sources: apps/web/lib/api/links/utils/process-key.ts:8-41
_root, it returns immediately. Sources: apps/web/lib/api/links/utils/process-key.ts:8-12validKeyRegex and rejects any key starting with an underscore _ (reserved for Dub internals) or flagged by isUnsupportedKey. Sources: apps/web/lib/api/links/utils/process-key.ts:13-25key.replace(/^\/+|\/+$/g, ""). Sources: apps/web/lib/api/links/utils/process-key.ts:26-28NFD) and strips accents/diacritical marks (/[\u0300-\u036f]/g) to prevent phishing and typo squatting. Sources: apps/web/lib/api/links/utils/process-key.ts:34-36punyEncode. Sources: apps/web/lib/api/links/utils/process-key.ts:37-40Warning
Keys starting with an underscore are strictly reserved for Dub internal routes and will cause processKey to return null, preventing custom links from utilizing leading underscores. Sources: apps/web/lib/api/links/utils/process-key.ts:17-20
When an edge lookup executes, domain case sensitivity determines how keys are prepared for querying:
POST extracts the hostname and pathname from the incoming request URL. Sources: apps/web/app/ee/api/track/open/route.ts:25-79getLinkViaEdge passes the domain and key into getLinkViaEdgeHelper. Sources: apps/web/lib/planetscale/get-link-via-edge.ts:46-68getLinkViaEdgeHelper evaluates isCaseSensitiveDomain(domain) to branch between case-sensitive encoding (encodeKey(key)) and non-case-sensitive punycode/URI decoding (punyEncode(safeDecodeURIComponent(key))). Sources: apps/web/lib/planetscale/get-link-via-edge.ts:10-24Sources: apps/web/app/ee/api/track/open/route.ts:75-97, apps/web/lib/planetscale/get-link-via-edge.ts:10-68
Sources: apps/web/lib/api/links/utils/process-key.ts:8-41, packages/utils/src/functions/link-constructor.ts:3-39, apps/web/lib/planetscale/get-link-via-edge.ts:10-39
Final URL assembly integrates base targets, A/B test variant splits, attribution parameters, and incoming query strings at the edge. The resolution routine evaluates cached link properties, resolves variant destinations via resolveABTestURL, and constructs the outgoing redirection destination using getFinalUrl. Sources: apps/web/lib/middleware/link.ts:168-173, apps/web/lib/middleware/utils/get-final-url.ts:12-23
The construction of a resolved destination URL flows through specific middleware and utility stages before returning a final redirect string:
LinkMiddleware extracts cached link properties including testVariants and testCompletedAt. Sources: apps/web/lib/middleware/link.ts:150-171resolveABTestURL evaluates the active variants to select a target URL if an A/B test is active. Sources: apps/web/lib/middleware/link.ts:168-171testUrl or cachedLink.url is passed to getFinalUrl along with the request object and clickId. Sources: apps/web/lib/middleware/link.ts:173getFinalUrl parses query parameters, injects attribution overrides (such as dub_id or Stripe parameters), and appends pass-through query parameters from the incoming request. Sources: apps/web/lib/middleware/utils/get-final-url.ts:25-125Sources: apps/web/lib/middleware/link.ts:168-173, apps/web/lib/middleware/utils/get-final-url.ts:12-125
Warning
Internal query parameters like dub-no-track and redirection control parameters (redir_url) are explicitly filtered out during pass-through parameter iteration, preventing them from leaking into external destination URLs. Sources: apps/web/lib/middleware/utils/get-final-url.ts:108-112
For partner program enrollments and external integrations, partner links undergo generation via generatePartnerLink where keys are derived from usernames, names, or emails, and integration-specific parameters are appended. Sources: apps/web/lib/api/partners/generate-partner-link.ts:16-40
Note
When appsFlyerParameters are supplied during partner link generation, generatePartnerLink processes target URLs via applyAppsFlyerParameters, interpolating partner context names and link keys directly into the attribution stream. Sources: apps/web/lib/api/partners/generate-partner-link.ts:147-161
When a requested link fails resolution due to expiration, missing records, or administrative enforcement, Dub routes requests to dedicated terminal pages or executes administrative restriction pipelines. Expired and not-found pages support custom domain-level redirect configurations, whereas banned links invoke backend administrative actions that isolate resources under legal compliance entities. Sources: apps/web/app/domain/expired/page.tsx:34-42, apps/web/app/domain/notfound/page.tsx:35-43, apps/web/app/ee/api/admin/links/ban/route.ts:14-46
The expired and not-found route handlers accept dynamic domain parameters, query the primary database via Prisma to inspect custom domain fallback properties, and conditionally trigger Next.js navigation redirects if configuration values exist. Sources: apps/web/app/domain/expired/page.tsx:30-42, apps/web/app/domain/notfound/page.tsx:31-43
Sources: apps/web/app/domain/expired/page.tsx:14-49, apps/web/app/domain/notfound/page.tsx:14-50, apps/web/app/domain/banned/page.tsx:12-34
Note
All three terminal page components export revalidate = false to cache responses indefinitely, and configure generateStaticParams() to return an empty array for on-demand static generation. Sources: apps/web/app/domain/expired/page.tsx:12-28, apps/web/app/domain/notfound/page.tsx:12-29, apps/web/app/domain/banned/page.tsx:10-25
The administrative link banning endpoint (DELETE /api/admin/links/ban) is protected by owner-level admin checks and processes incoming query parameters through the domain key schema to identify target resources. Sources: apps/web/app/ee/api/admin/links/ban/route.ts:13-20
export const DELETE = withAdmin(
async ({ searchParams }) => {
const { domain, key } = domainKeySchema.parse(searchParams);
const link = await prisma.link.findUnique({
where: { domain_key: { domain, key } },
});
if (!link) {
return NextResponse.json({ error: "Link not found" }, { status: 404 });
}
const urlDomain = getDomainWithoutWWW(link.url);
const response = await Promise.all([
prisma.link.update({
where: { id: link.id },
data: {
userId: LEGAL_USER_ID,
projectId: LEGAL_WORKSPACE_ID,
},
}),
linkCache.set({ ...link, projectId: LEGAL_WORKSPACE_ID }),
urlDomain && updateConfig({ key: "domains", value: urlDomain }),
]);
return NextResponse.json(response);
},
{ requiredRoles: ["owner"] },
);Warning
Banning a link reassigns its ownership properties (userId and projectId) to LEGAL_USER_ID and LEGAL_WORKSPACE_ID, immediately removing management access from standard workspace members while updating the edge cache and edge configuration domains. Sources: apps/web/app/ee/api/admin/links/ban/route.ts:28-46
The deep linking and preview subsystem controls mobile OS routing, app store redirection, iframe cloaking, and metadata proxy inspection. Mobile deep link handling starts at DeepLinkPreviewPage, which parses request headers, evaluates the client operating system via Next.js userAgent, and queries Prisma for domain-level asset configurations. Sources: apps/web/app/app.dub.co/deeplink/deeplink/domain/...key/page.tsx:45-90
When a request enters the deep link handler, the system performs a multi-step platform validation to determine whether to display a deep view preview card or trigger an immediate platform redirect. Sources: apps/web/app/app.dub.co/deeplink/deeplink/domain/...key/page.tsx:58-137
Warning
If a short domain lacks appleAppSiteAssociation or assetLinks configurations combined with valid deepviewData, the preview page is bypassed entirely, immediately issuing a server-side redirect to link.ios, link.android, or the canonical link.url. Sources: apps/web/app/app.dub.co/deeplink/deeplink/domain/...key/page.tsx:102-110
Dub provides dedicated routes for metadata proxying and link inspection, allowing clients to examine short links or render masked link previews safely via edge runtimes. Sources: apps/web/app/domain/key/proxy/page.tsx:1-34, apps/web/app/domain/key/inspect/page.tsx:1-39, apps/web/app/cloaked/url/page.tsx:1-38
Sources: apps/web/app/domain/key/proxy/page.tsx:1-34, apps/web/app/domain/key/inspect/page.tsx:1-39, apps/web/app/cloaked/url/page.tsx:1-38, apps/web/app/api/links/iframeable/route.ts:10-22
Tip
The /api/links/iframeable endpoint enforces rate limiting via ratelimitOrThrow(req, "iframeable") prior to invoking isIframeable to prevent abuse of external target inspection. Sources: apps/web/app/api/links/iframeable/route.ts:17-20
When requests hit the cloaked URL handler at apps/web/app/cloaked/[url]/page.tsx, getCloakedDestinationUrl evaluates whether the dynamic route parameter requires double-decoding. Sources: apps/web/app/cloaked/url/page.tsx:8-20
function getCloakedDestinationUrl(param: string): string {
if (/^https?%3A/i.test(param)) {
try {
return decodeURIComponent(param);
} catch {
return param;
}
}
return param;
}When an incoming link lookup results in a cache and edge database miss, the resolution pipeline invokes specialized fallback handlers to recover the link or ingest it dynamically. For legacy Bitly short links matching specific domains such as buff.ly, the link middleware delegates resolution to an on-demand crawler that queries the Bitly API, materializes the record into the local database, and issues a redirection. Sources: apps/web/lib/middleware/link.ts:100-117, apps/web/lib/middleware/utils/crawl-bitly.ts:20-68
Sources: apps/web/lib/middleware/link.ts:100-117, apps/web/lib/middleware/utils/crawl-bitly.ts:20-68
The crawlBitly utility inspects the request parameters and validates the key against unsupported character patterns before querying the remote Bitly API. Sources: apps/web/lib/middleware/utils/crawl-bitly.ts:20-28
const invalidBitlyKeyRegex = /[`~,.<>;':"/\\[\]^{}()=+!*@&$£?%#|]/;If the key is valid and exists in Bitly's system, the crawler extracts the long URL and persists a new link record asynchronously using ev.waitUntil. The newly created link is assigned to a designated Buffer workspace, user, and folder configuration using fixed system IDs. Sources: apps/web/lib/middleware/utils/crawl-bitly.ts:27-61
Warning
If the Bitly API rate limit is exceeded or the link cannot be found, fetchBitlyLink returns null, causing the crawler to redirect fallback traffic directly to https://buffer.com with a 24-hour cache control header. Sources: apps/web/lib/middleware/utils/crawl-bitly.ts:71-81
For workspace-level migrations and bulk operations, Bitly integration tokens are exchanged via OAuth and stored securely in Redis. The callback route at apps/web/app/api/callback/bitly/route.ts handles token exchange and redirects users back to their workspace slug with query parameters. Sources: apps/web/app/api/callback/bitly/route.ts:10-59
Sources: apps/web/lib/middleware/link.ts:89-147, apps/web/lib/middleware/utils/crawl-bitly.ts:15-61, apps/web/app/api/callback/bitly/route.ts:20-59