---
title: "Request Routing"
description: "Request routing in Dub is powered by a central edge middleware layer that intercepts incoming HTTP requests across all domains and subdomains, parses hostnames and paths, and delegates execution to..."
last_updated: "2026-10-12T07:00:10.095215+00:00"
canonical_url: "https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/technical/core-architecture/request-routing"
---

<details>
<summary>Relevant source files</summary>

The following files were used as context for generating this wiki page:

- [apps/web/middleware.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/middleware.ts)
- [apps/web/lib/middleware/link.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/link.ts)
- [apps/web/lib/middleware/partners.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/partners.ts)
- [apps/web/lib/middleware/app.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/app.ts)
- [apps/web/app/ee/api/partners/links/route.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/partners/links/route.ts)
- [apps/web/app/ee/api/track/application/route.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/track/application/route.ts)
- [apps/web/lib/middleware/admin.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/admin.ts)
- [apps/web/app/ee/api/embed/referrals/links/route.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/embed/referrals/links/route.ts)
- [apps/web/lib/middleware/workspaces.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/workspaces.ts)
- [apps/web/app/ee/api/cron/domains/update/route.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/cron/domains/update/route.ts)
- [apps/web/app/api/tokens/embed/referrals/route.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/api/tokens/embed/referrals/route.ts)
- [apps/web/app/ee/api/admin/impersonate/route.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/admin/impersonate/route.ts)
- [apps/web/lib/middleware/embed.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/embed.ts)
- [apps/web/lib/middleware/utils/crawl-bitly.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/utils/crawl-bitly.ts)
- [apps/web/lib/middleware/utils/parse.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/utils/parse.ts)
- [apps/web/ui/program-marketplace/external/marketplace-external-router.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/program-marketplace/external/marketplace-external-router.tsx)
- [apps/web/lib/middleware/utils/partners-redirect.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/utils/partners-redirect.ts)
- [apps/web/app/app.dub.co/marketplace/...segments/page.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/app.dub.co/marketplace/%5B%5B...segments%5D%5D/page.tsx)
- [apps/web/lib/middleware/api.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/api.ts)
- [apps/web/app/api/domains/search-availability/route.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/api/domains/search-availability/route.ts)
- [apps/web/ui/program-marketplace/marketplace-router.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/program-marketplace/marketplace-router.tsx)
- [apps/web/app/ee/api/admin/domains/search-availability/route.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/admin/domains/search-availability/route.ts)
- [packages/utils/src/constants/middleware.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/packages/utils/src/constants/middleware.ts)
- [apps/web/lib/middleware/utils/app-redirect.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/utils/app-redirect.ts)
- [apps/web/app/app.dub.co/onboarding/onboarding/steps/program/page.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/app.dub.co/(onboarding)/onboarding/(steps)/program/page.tsx)
- [apps/web/app/wellknown/domain/file/route.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/wellknown/%5Bdomain%5D/%5Bfile%5D/route.ts)
- [apps/web/lib/middleware/create-link.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/create-link.ts)
- [apps/web/lib/middleware/new-link.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/new-link.ts)
- [apps/web/app/ee/api/embed/referrals/token/route.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/embed/referrals/token/route.ts)
- [apps/web/lib/middleware/utils/is-top-level-settings-redirect.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/utils/is-top-level-settings-redirect.ts)
</details>

## Overview

Request routing in Dub is powered by a central edge middleware layer that intercepts incoming HTTP requests across all domains and subdomains, parses hostnames and paths, and delegates execution to specialized middleware handlers. This routing architecture solves multi-tenant separation, short link resolution, partner program routing, and administrative security enforcement at the edge before requests reach application routes. Key design decisions include executing lightweight path parsing and telemetry logging via Axiom prior to domain-specific branching, leveraging Redis caching and edge lookups for fast link redirection, and applying workspace context and authentication checks for platform apps. It interacts closely with edge caching layers, database lookups, and authentication guard utilities to ensure low-latency routing and secure access control across all core services.

Sources: [apps/web/middleware.ts:34-88](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/middleware.ts#L34-L88), [apps/web/lib/middleware/utils/parse.ts:5-54](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/utils/parse.ts#L5-L54)

## Central Dispatch and Request Parsing

### Middleware Entry and Request Matching

The root middleware acts as the primary public entry point for incoming HTTP traffic, operating on the Node.js runtime environment defined in `apps/web/middleware.ts`. Its routing scope is governed by a Next.js `matcher` configuration that intercepts all requests while explicitly excluding application programming interface endpoints, Next.js internal assets, third-party proxies, and standard metadata files such as favicons, sitemaps, robots declarations, and web manifests.

```typescript
export const config = {
  runtime: "nodejs",
  matcher: [
    /*
     * Match all paths except for:
     * 1. /api/ routes
     * 2. /_next/ (Next.js internals)
     * 3. /_proxy/ (proxies for third-party services)
     * 4. Metadata files: favicon.ico, sitemap.xml, robots.txt, manifest.webmanifest
     */
    "/((?!api/|_next/|_proxy/|favicon.ico|sitemap.xml|robots.txt|manifest.webmanifest).*)",
  ],
};
```
Sources: [apps/web/middleware.ts:20-32](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/middleware.ts#L20-L32)

Upon receiving a request, the middleware immediately invokes telemetry collection through Axiom by transforming the request and dispatching asynchronous log flushes via `ev.waitUntil()` before evaluating branch logic.

```typescript
  // Axiom logging
  logger.info(...transformMiddlewareRequest(req));
  ev.waitUntil(logger.flush());
```
Sources: [apps/web/middleware.ts:37-40](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/middleware.ts#L37-L40)

### Hostname Normalization and Parsing Utility

The `parse` utility function in `apps/web/lib/middleware/utils/parse.ts` extracts and normalizes core routing properties from the incoming `NextRequest`. It retrieves the `host` header, strips any leading `www.` prefix, converts the domain string to lowercase, and checks for domain-level mappings defined in `DOMAIN_REDIRECTS`.

```typescript
export const parse = (req: NextRequest) => {
  let domain = req.headers.get("host") as string;
  // path is the path of the URL (e.g. dub.sh/stats/github -> /stats/github)
  let path = req.nextUrl.pathname;

  // remove www. from domain and convert to lowercase
  domain = domain.replace(/^www./, "").toLowerCase();

  if (DOMAIN_REDIRECTS[domain]) {
    domain = DOMAIN_REDIRECTS[domain];
  }
```
Sources: [apps/web/lib/middleware/utils/parse.ts:1-15](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/utils/parse.ts#L1-L15)

> [!NOTE]
> End-to-end test request detection inspects local development hostnames (`dub.localhost:8888`) as well as Vercel preview environments where the `x-e2e-redirect-test` header is set to `true` with an application hostname. When matched, case-sensitive tests override the domain to `dub-internal-test.com`, while general preview requests resolve to the standard `SHORT_DOMAIN`.

Sources: [apps/web/lib/middleware/utils/parse.ts:17-34](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/utils/parse.ts#L17-L34)

Path components and search parameters are subsequently compiled into a structured routing context object. The primary link key and multi-level subpaths (`fullKey`) are decoded using `decodeURIComponent` to accommodate internationalized character sets such as Hebrew and Korean.

```typescript
  // fullPath is the full URL path (along with search params)
  const searchParams = req.nextUrl.searchParams.toString();
  const searchParamsObj = Object.fromEntries(req.nextUrl.searchParams);
  const searchParamsString = searchParams.length > 0 ? `?${searchParams}` : "";
  const fullPath = `${path}${searchParamsString}`;

  // Here, we are using decodeURIComponent to handle foreign languages like Hebrew and Korean
  const key = decodeURIComponent(path.split("/")[1]); // key is the first part of the path (e.g. dub.sh/stats/github -> stats)
  const fullKey = decodeURIComponent(path.slice(1)); // fullKey is the full path without the first slash (to account for multi-level subpaths, e.g. d.to/github/repo -> github/repo)

  return {
    domain,
    path,
    fullPath,
    key,
    fullKey,
    shortLink: `https://${domain}/${fullKey}`,
    searchParamsObj,
    searchParamsString,
  };
};
```
Sources: [apps/web/lib/middleware/utils/parse.ts:35-54](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/utils/parse.ts#L35-L54)

### Dispatch Execution Flow

Once parsing concludes in `apps/web/middleware.ts`, the request is routed through a prioritized sequence of domain checks and path conditions before delegating to specialized handlers.

```mermaid
flowchart TD
    A[Incoming Request] --> B[Parse Hostname & Path]
    B --> C{isAppHostname domain ?}
    C -- Yes --> D[AppMiddleware]
    C -- No --> E{API_HOSTNAMES.has domain ?}
    E -- Yes --> F[ApiMiddleware]
    E -- No --> G{path.startsWith /stats/ ?}
    G -- Yes --> H[Rewrite to Stats Destination]
    G -- No --> I{path.startsWith /.well-known/ ?}
    I -- Yes --> J[Rewrite to Well-Known Path]
    I -- No --> K{domain dub.sh & DEFAULT_REDIRECTS key ?}
    K -- Yes --> L[Redirect Default Target]
    K -- No --> M{ADMIN_HOSTNAMES.has domain ?}
    M -- Yes --> N[AdminMiddleware]
    M -- No --> O{PARTNERS_HOSTNAMES.has domain ?}
    O -- Yes --> P[PartnersMiddleware]
    O -- No --> Q{isValidUrl fullKey ?}
    Q -- Yes --> R[CreateLinkMiddleware]
    Q -- No --> S[LinkMiddleware]
```
Sources: [apps/web/middleware.ts:34-88](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/middleware.ts#L34-L88)

## Application and Workspace Subdomain Routing

### App Middleware Request Validation

Requests arriving at application domains pass through `AppMiddleware` in `apps/web/lib/middleware/app.ts`. The middleware first checks if the path matches `/embed`, immediately delegating to `EmbedMiddleware`. Otherwise, it identifies the authenticated user via `getUserViaToken(req)`. If no user is present and the requested path is not public—such as `/marketplace`, paths starting with `/share/`, `/deeplink/`, `/unsubscribe/`, or `/auth/reset-password/`—the request redirects to `/login` with a `?next=` query parameter preserving the original destination.

```typescript
const isPublicPath = (path: string) =>
  ["/marketplace"].some((p) => path === p) ||
  [
    "/marketplace/",
    "/share/",
    "/deeplink/",
    "/unsubscribe/",
    "/auth/reset-password/",
  ].some((p) => path.startsWith(p));
```
Sources: [apps/web/lib/middleware/app.ts:16-24](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/app.ts#L16-L24)

For authenticated users accessing non-public paths, visiting `/new` invokes `NewLinkMiddleware`. Users within their initial onboarding window—evaluated via `ONBOARDING_WINDOW_SECONDS` and cached in `onboardingStepCache`—undergo onboarding flow validation unless they visit `/onboarding`, `/account`, or `/workspaces`, or possess a default workspace or pending workspace invites.

> [!NOTE]
> If an onboarding user already possesses a default workspace, the `workspace` onboarding step is automatically bypassed and advanced to `link`.

Sources: [apps/web/lib/middleware/app.ts:65-91](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/app.ts#L65-L91)

### Workspace Resolution and Path Mapping

When standard users request root or core application paths, `WorkspacesMiddleware` resolves their active context. It inspects query parameters for a validated `next` redirect path using `isValidInternalRedirect`. If the user has a default workspace retrieved via `getDefaultWorkspace(user)`, the middleware rewrites top-level application views and settings routes into workspace-scoped paths.

| Target Path Pattern | Route Condition | Resolved Workspace Destination |
| :--- | :--- | :--- |
| Root & Auth | `["/", "/login", "/register"].includes(path)` | `/[defaultWorkspace][product]` (derived via `getWorkspaceProduct`) |
| Top-Level Settings | `isTopLevelSettingsRedirect(path)` | `/[defaultWorkspace]/settings/[path]` |
| Standard Pages | `/links`, `/analytics`, `/events`, `/settings`, etc. | `/[defaultWorkspace]/[path]` |

Sources: [apps/web/lib/middleware/workspaces.ts:24-46](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/workspaces.ts#L24-L46)

> [!WARNING]
> Top-level settings redirects are strictly governed by `isTopLevelSettingsRedirect`, matching `/domains`, `/integrations`, `/webhooks`, and their respective child subpaths.

Sources: [apps/web/lib/middleware/utils/is-top-level-settings-redirect.ts:1-8](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/utils/is-top-level-settings-redirect.ts#L1-L8)

If no default workspace exists, `WorkspacesMiddleware` queries `prismaEdge.projectInvite` for matching user email records. If a pending project invite exists, the user redirects to `/[projectSlug]/invite`; otherwise, they route to `/onboarding/workspace`.

```typescript
  const projectInvite = await prismaEdge.projectInvite.findFirst({
    where: {
      email: user.email,
    },
    select: {
      project: {
        select: {
          slug: true,
        },
      },
    },
  });

  if (projectInvite) {
    return NextResponse.redirect(
      new URL(`/${projectInvite.project.slug}/invite`, req.url),
    );
  }

  return NextResponse.redirect(new URL("/onboarding/workspace", req.url));
```
Sources: [apps/web/lib/middleware/workspaces.ts:48-70](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/workspaces.ts#L48-L70)

### Legacy Path Redirection

The `appRedirect` utility normalizes legacy URLs, account paths, and restructured partner program routes before rendering. Except for excluded paths like `/workspaces`, legacy endpoints map to their updated counterparts.

```typescript
const EXCLUDED_PAGES = ["/workspaces"];

const APP_REDIRECTS = {
  "/account": "/account/settings",
  "/referrals": "/account/settings/referrals",
  "/onboarding": "/onboarding/welcome",
  "/welcome": "/onboarding/welcome",
  "/campaigns": "/program/campaigns",
  "/messages": "/program/messages",
  "/fraud": "/program/risks",
  "/risks": "/program/risks",
};
```
Sources: [apps/web/lib/middleware/utils/app-redirect.ts:1-15](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/utils/app-redirect.ts#L1-L15)

Additional regex patterns handle dynamic workspace product paths, upgrading billing routes (`/upgrade` to `/settings/billing/upgrade`), tracking guides, and migrating pluralized program routes (`/programs/*` to `/program/*`). Unmatched requests that bypass these rules fall through to a rewrite against `app.dub.co`.

Sources: [apps/web/lib/middleware/app.ts:128-130](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/app.ts#L128-L130), [apps/web/lib/middleware/utils/app-redirect.ts:37-125](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/utils/app-redirect.ts#L37-L125)

## Partner Portal and Marketplace Routing

### Partner Portal Authentication and Redirection Middleware

The partner portal middleware (`PartnersMiddleware`) manages request flow for partner domains, enforcing authentication on restricted paths such as `/programs`, `/marketplace`, `/onboarding`, `/settings`, `/profile`, `/messages`, `/payouts`, `/account`, `/invite`, and `/rewind`. When unauthenticated users request these paths, the middleware intercepts them. If the path begins with `/programs/`, it extracts the program slug to redirect to `/[programSlug]/login`; otherwise, it redirects to `/login` with an encoded `?next=` parameter containing the original path.

```typescript
const AUTHENTICATED_PATHS = [
  "/programs",
  "/marketplace",
  "/onboarding",
  "/settings",
  "/profile",
  "/messages",
  "/payouts",
  "/account",
  "/invite",
  "/rewind",
];
```
Sources: [apps/web/lib/middleware/partners.ts:12-23](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/partners.ts#L12-L23)

For authenticated users, the middleware verifies whether a default partner ID exists via `getDefaultPartnerId(user)`. If no partner ID is found, and the path is not a partner invite, onboarding, or account route, the user redirects to `/onboarding`. Furthermore, standard redirects map deprecated or legacy partner paths to their current equivalents.

| Legacy / Source Path Pattern | Target Destination |
| :--- | :--- |
| `/settings` | `/profile` |
| `/settings/payouts` | `/payouts` |
| `/settings/notifications` | `/profile/notifications` |
| `/account/settings/notifications` | `/profile/notifications` |
| `/profile/sites` | `/profile` |
| `/rewind` | `/rewind/2025` |
| `/onboarding/online-presence` | `/onboarding/platforms` |
| `/onboarding/verify` | `/onboarding/payouts` |

Sources: [apps/web/lib/middleware/utils/partners-redirect.ts:1-10](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/utils/partners-redirect.ts#L1-L10)

> [!WARNING]
> When handling `?next=` query parameters for authenticated users, the middleware validates destinations using `isValidInternalRedirect` to prevent open redirect vulnerabilities, explicitly omitting `/onboarding` paths from automatic immediate redirection to ensure prerequisite setup steps are completed.

Sources: [apps/web/lib/middleware/partners.ts:91-102](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/partners.ts#L91-L102)

### Program Slug Aliasing and Marketplace Segments

The partners utility routing layer handles structural transformations for [partner programs](https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/technical/partner-programs/partner-programs) and marketplace categories. Specifically, it maps legacy program identifiers to their current canonical slugs, normalizing references during redirection.

| Program Alias | Canonical Slug |
| :--- | :--- |
| `florafauna` | `flora` |
| `ship-30` | `dwp` |
| `supercutai` | `supercut` |
| `teller-perps` | `teller` |
| `hundred-health` | `hundred` |
| `gitroom-short` | `postiz` |
| `getviktor-com` | `viktor` |
| `getviktor` | `viktor` |
| `heynavii` | `navii` |
| `galaxyai` | `magica` |
| `voice-os` | `voiceos` |
| `speechify-inc` | `speechifyai` |
| `ggms-labs-ltd-ai` | `mira` |
| `missioncontrolhq` | `squad-so` |
| `nozomio` | `folk` |
| `base` | `coinbasewallet` |

Sources: [apps/web/lib/middleware/utils/partners-redirect.ts:36-53](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/utils/partners-redirect.ts#L36-L53)

Marketplace paths under `app/app.dub.co/marketplace/[[...segments]]/page.tsx` parse segment parameters to render either internal workspace views via `MarketplaceRouter` or external views via `MarketplaceExternalRouter`. The external router processes segments to determine layout rendering based on path depth and prefix matching.

```mermaid
flowchart TD
    Start([Segments Input]) --> CheckLen{Length?}
    CheckLen -->|0| Home[MarketplaceExternalHomePage]
    CheckLen -->|1 & 'all'| ListAll[MarketplaceExternalListPage]
    CheckLen -->|2 & 'c'| CategorySlug[slugToCategory lookup]
    CategorySlug --> ValidCat{Category Found?}
    ValidCat -->|Yes| ListCat[MarketplaceExternalListPage with fixedCategory]
    ValidCat -->|No| NotFound[notFound]
    CheckLen -->|1 slug| ProgPage[MarketplaceExternalProgramPage]
    CheckLen -->|Other| NotFound
```
Sources: [apps/web/ui/program-marketplace/external/marketplace-external-router.tsx:7-37](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/program-marketplace/external/marketplace-external-router.tsx#L7-L37)

## Link Redirect and Crawler Delegation

### Short Link Interception and Resolution

Incoming short link requests are intercepted at the edge within `LinkMiddleware` (detailed further in [Link Dispatch](https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/technical/link-attribution/link-dispatch)), where the request's hostname and path are parsed. The system handles case sensitivity by checking `isCaseSensitiveDomain(domain)`, converting keys to lowercase if the domain is case-insensitive, and performing Punycode encoding via `punyEncode`. Inspect mode, triggered by a trailing `+` character on the key, is stripped for lookup and later routes the request to an inspection view if the link lacks password protection. Requests with unsupported keys, such as `.php` paths prone to high bot traffic, receive an immediate 302 redirect to the root domain accompanied by `X-Robots-Tag: googlebot: noindex` and a `dub-no-track` parameter.

```typescript
if (isUnsupportedKey(key)) {
  return NextResponse.redirect(new URL("/?dub-no-track=1", req.url), {
    headers: {
      ...DUB_HEADERS,
      "X-Robots-Tag": "googlebot: noindex",
    },
    status: 302,
  });
}
```
Sources: [apps/web/lib/middleware/link.ts:77-87](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/link.ts#L77-L87)

When a link key evaluates to an empty string, it is normalized to `_root` to service root domain links. The middleware first queries Redis via `linkCache.get({ domain, key })` (documented in [Caching & Rate Limiting](https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/technical/core-architecture/caching-rate-limiting)). If the cache lookup misses, it falls back to PlanetScale using `getLinkViaEdge({ domain, key })` (see [Database Architecture](https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/technical/core-architecture/database-architecture)). If the link is still missing and the domain is specifically `buff.ly`, the router delegates control to the Bitly migration handler. Otherwise, it issues a rewrite to `/[domain]/notfound` with static page caching headers configured for a `s-maxage` of 86400 seconds.

```typescript
if (!cachedLink) {
  let linkData = await getLinkViaEdge({
    domain,
    key,
  });

  if (!linkData) {
    if (domain === "buff.ly") {
      return await crawlBitly(req, ev);
    }

    return NextResponse.rewrite(new URL(`/${domain}/notfound`, req.url), {
      headers: {
        ...DUB_HEADERS,
        ...STATIC_PAGES_CACHE_HEADERS,
      },
    });
  }
```
Sources: [apps/web/lib/middleware/link.ts:100-117](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/link.ts#L100-L117)

> [!NOTE]
> During Redis failover conditions, `linkCache.get` flags `redisFailOver` as true. The middleware automatically bypasses click tracking and click ID cookie lookups to protect downstream response latencies from timing out.

Sources: [apps/web/lib/middleware/link.ts:89-99](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/link.ts#L89-L99), [apps/web/lib/middleware/link.ts:193-211](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/link.ts#L193-L211)

### Bitly Migration Hooks and Crawler Delegation

When short link resolution encounters an unknown key on the `buff.ly` domain, `crawlBitly` executes a programmatic crawl against the Bitly API to fetch the backing long URL on-demand. Before querying Bitly, the handler validates that the key does not contain restricted Bitly characters matching the regular expression `invalidBitlyKeyRegex`.

```typescript
// bitly doesn't support the following characters: ` ~ , . < > ; ‘ : " / \ [ ] ^ { } ( ) = + ! * @ & $ £ ? % # |
// @see: https://support.bitly.com/hc/en-us/articles/360030780892-What-characters-are-supported-when-customizing-links
const invalidBitlyKeyRegex = /[`~,.<>;':"/\\[\]^{}()=+!*@&$£?%#|]/;

if (key && !invalidBitlyKeyRegex.test(key)) {
  const link = await fetchBitlyLink({ domain, key });
```
Sources: [apps/web/lib/middleware/utils/crawl-bitly.ts:23-28](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/utils/crawl-bitly.ts#L23-L28)

If a valid Bitly link object is returned, the long URL is sanitized using `getUrlFromStringIfValid`. The middleware then asynchronously provisions a new link record inside Prisma tied to a designated Buffer workspace, user, and folder, while concurrently recording the link and publishing a usage event via Upstash Redis streams. Finally, it redirects the client to the resolved long URL with a 302 status. If the Bitly API encounters a rate limit or returns no record, the utility defaults to a 302 redirection toward `https://buffer.com` with static CDN caching headers.

| Constant Name | Identifier / Value | Purpose |
| :--- | :--- | :--- |
| `BUFFER_WORKSPACE_ID` | `cm05wnnpo000711ztj05wwdbu` | Target workspace assignment for crawled Bitly links |
| `BUFFER_USER_ID` | `cm05wnd49000411ztg2xbup0i` | Creator user association for on-demand links |
| `BUFFER_FOLDER_ID` | `fold_1JNQBVZV8P0NA0YGB11W2HHSQ` | Default folder placement for migrated links |
| `BUFFER_BITLY_API_KEY` | `process.env.BUFFER_BITLY_API_KEY` | Bearer token credential for Bitly v4 API requests |

Sources: [apps/web/lib/middleware/utils/crawl-bitly.ts:15-18](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/utils/crawl-bitly.ts#L15-L18), [apps/web/lib/middleware/utils/crawl-bitly.ts:45-50](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/utils/crawl-bitly.ts#L45-L50)

### Well-Known Path Routing

Domain configuration files required by native applications and deep-linking services are resolved dynamically via the well-known route handler at `app/wellknown/[domain]/[file]/route.ts`. The route accepts requests parameterized by a `domain` slug and a `file` name, validating the requested file against a whitelist of supported files.

```typescript
if (!supportedWellKnownFiles.includes(file)) {
  return NextResponse.json({ error: "File not supported" }, { status: 400 });
}
```
Sources: [apps/web/app/wellknown/domain/file/route.ts:16-18](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/wellknown/%5Bdomain%5D/%5Bfile%5D/route.ts#L16-L18)

The handler queries PlanetScale for the domain's corresponding `appleAppSiteAssociation` and `assetLinks` fields. Depending on the requested file identifier, it constructs a JSON payload containing either Apple app site association data with default empty app links structure or Android asset links configuration, returning the response with public Vercel CDN caching headers.

```typescript
let response: WellKnownConfig[SupportedWellKnownFiles];
switch (file) {
  case "apple-app-site-association":
    response =
      (appleAppSiteAssociation as WellKnownConfig["apple-app-site-association"]) || {
        applinks: {
          apps: [],
          details: [],
        },
      };
    break;
  case "assetlinks.json":
    response = (assetLinks as WellKnownConfig["assetlinks.json"]) || [];
    break;
}
```
Sources: [apps/web/app/wellknown/domain/file/route.ts:34-48](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/wellknown/%5Bdomain%5D/%5Bfile%5D/route.ts#L34-L48)

## Administrative Path Access Control

### Middleware-Level Gatekeeping and Session Verification

The `AdminMiddleware` function guards internal moderation and tools in [Admin Dashboard](https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/technical/administration-and-api/admin-dashboard) by intercepting incoming requests, parsing path parameters, and validating session tokens via `getUserViaToken` (integrated with [Authentication](https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/technical/core-architecture/authentication)). If no authenticated user session exists and the target path differs from `/login`, the request receives a 302 redirect toward `/login`. The file `apps/web/app/ee/api/admin/impersonate/route.ts` provides user and workspace lookup capabilities for the admin portal, documented in [Admin Dashboard](https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/technical/administration-and-api/admin-dashboard).

```typescript
export async function AdminMiddleware(req: NextRequest) {
  const { path } = parse(req);

  const user = await getUserViaToken(req);

  if (!user && path !== "/login") {
    return NextResponse.redirect(new URL("/login", req.url));
```
Sources: [apps/web/lib/middleware/admin.ts:8-14](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/admin.ts#L8-L14)

When an authenticated user session is present, the middleware queries `prismaEdge.projectUsers` for a matching record where the user ID corresponds to the session and the project ID equals `DUB_WORKSPACE_ID`. If the user is absent from the designated administrative workspace, the handler passes the request through via `NextResponse.next()`, which surfaces a 404 error page.

```typescript
} else if (user) {
  const isAdminUser = await prismaEdge.projectUsers.findUnique({
    where: {
      userId_projectId: {
        userId: user.id,
        projectId: DUB_WORKSPACE_ID,
      },
    },
  });

  if (!isAdminUser) {
    return NextResponse.next(); // throw 404 page
```
Sources: [apps/web/lib/middleware/admin.ts:15-26](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/admin.ts#L15-L26)

> [!CAUTION]
> Returning `NextResponse.next()` for non-workspace users intentionally triggers a 404 Not Found response rather than a 403 Forbidden error, preventing information disclosure regarding the existence of administrative endpoints.

Sources: [apps/web/lib/middleware/admin.ts:25-26](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/admin.ts#L25-L26)

### Route-Level Authorization Guards

For workspace-verified administrators, further route-level validation applies. If the requested path is `/login` or fails the `canAccessAdminPath` guard check, the middleware redirects the client to the root URL `/`.

```typescript
  } else if (
    path === "/login" ||
    !canAccessAdminPath({ userId: user.id, pathname: path })
  ) {
    return NextResponse.redirect(new URL("/", req.url));
  }
}
```
Sources: [apps/web/lib/middleware/admin.ts:27-33](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/admin.ts#L27-L33)

If all authorization checks pass, the middleware routes the request internally by rewriting the destination URL to the internal administrative subdomain `admin.dub.co`, preserving or normalizing the trailing path segments.

```typescript
return NextResponse.rewrite(
  new URL(`/admin.dub.co${path === "/" ? "" : path}`, req.url),
);
```
Sources: [apps/web/lib/middleware/admin.ts:35-37](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/admin.ts#L35-L37)

## Embedded Components and Quick-Action Handlers

### Standalone Link Creators and Widget Embeds

Requests targeting embedded widgets, quick-action link creators, and referral portals are processed through distinct middleware and API routes. The `EmbedMiddleware` function parses incoming requests and inspects their path or search parameters. If a request begins with `/embed/support-chat` or includes a `token` search parameter, it rewrites the destination to the primary application domain at `/app.dub.co`. Requests lacking these valid parameters are redirected to the root URL `/`.

```typescript
export function EmbedMiddleware(req: NextRequest) {
  const { path, searchParamsObj, fullPath } = parse(req);

  if (path.startsWith("/embed/support-chat")) {
    return NextResponse.rewrite(new URL(`/app.dub.co${fullPath}`, req.url));
  }

  if (searchParamsObj.token) {
    return NextResponse.rewrite(new URL(`/app.dub.co${fullPath}`, req.url));
  }

  // TODO: Show token expiry page
  return NextResponse.redirect(new URL("/", req.url));
```
Sources: [apps/web/lib/middleware/embed.ts:4-16](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/embed.ts#L4-L16)

Shortcuts that instantiate link generation from external contexts rely on the `CreateLinkMiddleware`. This utility extracts the domain and full path from the request, decodes the target URL string, and redirects the client to the `/new` endpoint on the application domain with `link` and `domain` query parameters attached.

```typescript
export function CreateLinkMiddleware(req: NextRequest) {
  const { domain, fullPath } = parse(req);

  const url = getUrlFromString(fullPath.slice(1));

  const redirectURL = new URL(`${APP_DOMAIN}/new`);
  redirectURL.searchParams.append("link", url);
  redirectURL.searchParams.append("domain", domain);

  return NextResponse.redirect(redirectURL.toString());
```
Sources: [apps/web/lib/middleware/create-link.ts:5-14](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/create-link.ts#L5-L14)

Once a user interacts with the link creation shortcut, `NewLinkMiddleware` resolves the user's default workspace using `getDefaultWorkspace`. If a workspace exists, it redirects the client to the workspace's link management page with query arguments passing the link parameters forward; otherwise, it directs unassigned users to the workspace creation flow.

```typescript
export async function NewLinkMiddleware(req: NextRequest, user: UserProps) {
  const { fullPath } = parse(req);

  const defaultWorkspace = await getDefaultWorkspace(user);

  const searchParams = new URL(fullPath, APP_DOMAIN).searchParams;

  if (defaultWorkspace) {
    return NextResponse.redirect(
      new URL(
        `/${defaultWorkspace}/links?newLink=${searchParams.get("link") || true}${searchParams.has("domain") ? `&newLinkDomain=${searchParams.get("domain")}` : ""}`,
        req.url,
      ),
    );
  } else {
    return NextResponse.redirect(
      new URL(`/workspaces?newWorkspace=true`, req.url),
    );
  }
```
Sources: [apps/web/lib/middleware/new-link.ts:7-25](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/new-link.ts#L7-L25)

> [!NOTE]
> `NewLinkMiddleware` validates the presence of an active user workspace before constructing redirect URLs, ensuring users without a designated default workspace are forced through the `/workspaces?newWorkspace=true` onboarding path.

Sources: [apps/web/lib/middleware/new-link.ts:14-25](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/middleware/new-link.ts#L14-L25)

### Referral Widget API Routes

Referral program embeds interact with authenticated backend handlers to retrieve and provision tracking links. The endpoint at `/api/embed/referrals/links` uses `withReferralsEmbedToken` to govern both read and write operations.

| HTTP Method | Route Handler | Authentication / Guard | Action & Validation |
| :--- | :--- | :--- | :--- |
| `GET` | `/api/embed/referrals/links` | `withReferralsEmbedToken` | Parses and returns existing partner links via `ReferralsEmbedLinkSchema`. |
| `POST` | `/api/embed/referrals/links` | `withReferralsEmbedToken` | Verifies active enrollment status, checks link count limits against `group.maxPartnerLinks`, and processes new links. |
| `GET` | `/api/embed/referrals/token` | `withReferralsEmbedToken` | Returns the validated active embed token object. |
| `POST` | `/api/tokens/embed/referrals` | `withWorkspace` (Required: `links.write`, `advanced`/`enterprise`) | Creates or retrieves program enrollment and provisions a new referrals embed token. |

Sources: [apps/web/app/ee/api/embed/referrals/links/route.ts:18-158](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/embed/referrals/links/route.ts#L18-L158), [apps/web/app/ee/api/embed/referrals/token/route.ts:4-7](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/embed/referrals/token/route.ts#L4-L7), [apps/web/app/api/tokens/embed/referrals/route.ts:15-121](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/api/tokens/embed/referrals/route.ts#L15-L121)

When creating partner links via the embed referral POST handler, the system ensures the partner's enrollment status is included in `ACTIVE_ENROLLMENT_STATUSES` and that the requested URL satisfies group constraints via `validatePartnerLinkUrl`.

```typescript
if (!ACTIVE_ENROLLMENT_STATUSES.includes(programEnrollment.status)) {
  throw new DubApiError({
    code: "forbidden",
    message:
      "You cannot create links in this program because your enrollment is not active.",
  });
}

const { url, key } = createPartnerLinkSchema
  .pick({ url: true, key: true })
  .parse(await parseRequestBody(req));

if (!program.domain || !program.url) {
  throw new DubApiError({
    code: "bad_request",
    message:
      "This program needs a domain and URL set before creating a link.",
  });
}

if (links.length >= group.maxPartnerLinks) {
  throw new DubApiError({
    code: "bad_request",
    message: `You have reached the limit of ${group.maxPartnerLinks} program links.`,
  });
}

validatePartnerLinkUrl({ group, url });

```
Sources: [apps/web/app/ee/api/embed/referrals/links/route.ts:28-56](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/embed/referrals/links/route.ts#L28-L56)

## Related

- [Link Dispatch](https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/technical/link-attribution/link-dispatch)
- [Caching & Rate Limiting](https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/technical/core-architecture/caching-rate-limiting)


## Sitemap

See the full [sitemap](https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/llms.txt) for all pages in this wiki.
