---
title: "Path Routing Deep Dive"
description: "Understand how Vercel routes requests to microfrontends and why `proxy.ts` behavior changes."
canonical_url: "https://vercel.com/academy/microfrontends-on-vercel/path-routing"
md_url: "https://vercel.com/academy/microfrontends-on-vercel/path-routing.md"
docset_id: "vercel-academy"
doc_version: "1.0"
last_updated: "2026-11-04T22:26:55.217Z"
content_type: "lesson"
course: "microfrontends-on-vercel"
course_title: "Microfrontends on Vercel"
prerequisites:  []
---

<agent-instructions>
Vercel Academy — structured learning, not reference docs.
Lessons are sequenced.
Adapt commands to the human's actual environment (OS, package manager, shell, editor) — detect from project context or ask, don't assume.
The lesson shows one path; if the human's project diverges, adapt concepts to their setup.
Preserve the learning goal over literal steps.
Quizzes are pedagogical — engage, don't spoil.
Quiz answers are included for your reference.
</agent-instructions>

# Path Routing Deep Dive

# 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](https://vercel.com/docs/microfrontends/troubleshooting#validatemiddlewareconfig) 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:

```typescript title="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:

```typescript title="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 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

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`:

```typescript title="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:

```json
"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:

```bash
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 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:

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

```bash
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`.


---

[Full course index](/academy/llms.txt) · [Sitemap](/academy/sitemap.md)
