Vercel Logo

Path Routing Deep Dive

Microfrontends routing happens before your application code runs. This changes everything about how proxy.ts (formerly middleware), redirects, and rewrites behave.

Outcome

Understand the routing execution order and how path isolation affects proxy.ts, redirects, and rewrites.

The Execution Order

When a request hits Vercel, it passes through these layers in order:

Request: GET /docs/api/overview

1. Firewall        → Platform-wide firewall, then WAF rules
2. MICROFRONTENDS  → Routes to correct project (/docs/* → docs app)
3. Headers         → Response headers for docs app
4. Redirects       → Redirect rules in docs app
5. proxy.ts        → Default app only, so /docs skips it
6. Rewrites        → Rewrite rules in docs app
7. Route Handler   → Actual page renders

The key insight: Microfrontends routing happens at step 2. By step 5, the request already belongs to the docs app, and the microfrontends docs describe proxy (middleware) as running only in the default application.

The Proxy Shift

Next.js 16 renamed middleware.ts to proxy.ts and the exported function to proxy. The job didn't change, and neither did the habit of putting every cross-cutting check in it. Before microfrontends:

proxy.ts
import type { NextRequest } from "next/server";
 
export function proxy(request: NextRequest) {
  // This ran for EVERY request
  if (request.nextUrl.pathname.startsWith("/app")) {
    // Check auth
  }
}

After microfrontends, the same file in the default app sees a lot less:

apps/marketing/proxy.ts
import type { NextRequest } from "next/server";
 
export function proxy(request: NextRequest) {
  // Only paths marketing serves: /, /pricing, and anything unclaimed
  // Requests to /docs/* and /app/* never reach this file
}

If you're used to a single central proxy for auth or logging, that pattern breaks. Vercel sends /app straight to the dashboard, so the auth check for /app has to live in the dashboard's own server code.

Path Isolation Explained

After microfrontends routing, each app is isolated:

Request PathRouted ToApp Only Sees
/marketing/
/pricingmarketing/pricing
/docsdocs/docs
/docs/apidocs/docs/api
/appdashboard/app
/settingsdashboard/settings

The docs app will never receive a request for /pricing. The marketing app will never see /app/projects.

Fast Track

  1. Review the execution order diagram above
  2. Understand that each app only sees its own paths
  3. Add a redirect to test path routing behavior

Hands-on Exercise 2.2

Test path isolation by adding a redirect that crosses app boundaries.

Part 1: Add a Cross-App Redirect

Add a redirect to apps/marketing/next.config.ts:

apps/marketing/next.config.ts
import type { NextConfig } from "next";
import { withMicrofrontends } from "@vercel/microfrontends/next/config";
 
const nextConfig: NextConfig = {
  redirects: async () => [
    {
      source: "/documentation",
      destination: "/docs",
      permanent: true,
    },
  ],
};
 
export default withMicrofrontends(nextConfig);

Part 2: Understand What Happens

When you visit /documentation:

  1. Request arrives at Vercel
  2. Microfrontends checks: does /documentation match any routing rules?
  3. No match for /docs/:path* (different path), so it goes to marketing (default)
  4. Marketing's redirect fires: /documentation → /docs
  5. Browser makes new request for /docs
  6. Microfrontends routes /docs to the docs app
  7. Docs app serves the page

Key insight: The redirect works because /documentation routes to marketing first, where the redirect is defined.

Part 3: What Would Break

If you added /documentation to the docs app's routing:

"acme-docs": {
  "routing": [
    { "paths": ["/docs", "/docs/:path*", "/documentation"] }
  ]
}

Now the redirect in marketing would never run because /documentation goes directly to docs. The redirect lives in marketing, but the path routes to docs.

Try It

Start the apps and test the redirect:

pnpm dev
  1. Visit http://localhost:3024/documentation (the microfrontends proxy port, so every app is reachable)
  2. You should be redirected to /docs
  3. The docs app serves the page

Why Redirects Can "Disappear"

A common frustration: "I added a redirect, but it doesn't work!"

The rule: Redirects only run in the app that receives the request.

Redirect LocationPath Routes ToDoes Redirect Run?
marketingmarketing✅ Yes
marketingdocs❌ No
docsdocs✅ Yes
docsmarketing❌ No

If your redirect isn't working, check which app the path routes to.

Debug Headers

In production, enable debug headers to see routing decisions:

HeaderDescription
x-vercel-mfe-appWhich app handled the request
x-vercel-mfe-matched-pathWhich pattern matched
x-vercel-mfe-target-deployment-idDeployment that served it

Enable debug mode:

  1. Set cookie: VERCEL_MFE_DEBUG=1
  2. Or use Vercel Toolbar → Microfrontends panel → Enable Debug Mode

We'll use these in Lesson 3.3 (Observability).

Commit

git add -A
git commit -m "feat: add redirect to demonstrate path routing"

Done-When

  • You can explain the execution order: Firewall → MFE → Headers → Redirects → proxy.ts → Rewrites
  • You understand that each app only sees requests for its own paths
  • You know why redirects can "disappear" (defined in wrong app)
  • You've tested the /documentation → /docs redirect

The Pattern for Shared Logic

The default app's proxy never sees child paths, so how do you share logic like authentication?

Options:

  1. Shared package - Put the helpers in @acme/utils and import them in every app that needs them
  2. Server Components/Actions - Call those helpers from each app's layout.tsx, route handlers, or Server Actions (recommended in Next.js 16)

We'll build the shared package in Lesson 2.4.

What's Next

Enable the local proxy and see all three apps stitched together at localhost:3024.

Was this helpful?

supported.