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:
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:
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 Path | Routed To | App Only Sees |
|---|---|---|
/ | marketing | / |
/pricing | marketing | /pricing |
/docs | docs | /docs |
/docs/api | docs | /docs/api |
/app | dashboard | /app |
/settings | dashboard | /settings |
The docs app will never receive a request for /pricing. The marketing app will never see /app/projects.
Fast Track
- Review the execution order diagram above
- Understand that each app only sees its own paths
- 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:
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:
- Request arrives at Vercel
- Microfrontends checks: does
/documentationmatch any routing rules? - No match for
/docs/:path*(different path), so it goes to marketing (default) - Marketing's redirect fires:
/documentation→/docs - Browser makes new request for
/docs - Microfrontends routes
/docsto the docs app - 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- Visit http://localhost:3024/documentation (the microfrontends proxy port, so every app is reachable)
- You should be redirected to
/docs - 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 Location | Path Routes To | Does Redirect Run? |
|---|---|---|
| marketing | marketing | ✅ Yes |
| marketing | docs | ❌ No |
| docs | docs | ✅ Yes |
| docs | marketing | ❌ 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:
| Header | Description |
|---|---|
x-vercel-mfe-app | Which app handled the request |
x-vercel-mfe-matched-path | Which pattern matched |
x-vercel-mfe-target-deployment-id | Deployment that served it |
Enable debug mode:
- Set cookie:
VERCEL_MFE_DEBUG=1 - 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→/docsredirect
The Pattern for Shared Logic
The default app's proxy never sees child paths, so how do you share logic like authentication?
Options:
- Shared package - Put the helpers in
@acme/utilsand import them in every app that needs them - 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?