← Files UniformARCHIVED FILE
skills/uniform-nextjs-app-router/references/routing.md
6.35 KB · Oct 3, 2026 · 06:34 UTC
# Routing and middleware
The middleware is the most critical part of the integration. It runs at the edge and handles route resolution, personalization evaluation, and request rewriting. See `references/setup.md` for the required basic middleware and the Next.js 16 `runtime: "experimental-edge"` requirement.
## Using handleUniformRoute directly
For more control than `uniformMiddleware()`, use `handleUniformRoute`:
```ts
import { handleUniformRoute } from "@uniformdev/next-app-router/middleware";
import { NextRequest } from "next/server";
export default (request: NextRequest) => {
return handleUniformRoute({ request });
};
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
runtime: "experimental-edge",
};
```
## Middleware options
| Option | Type | Description |
|--------|------|-------------|
| `rewriteRequestPath` | `(options) => Promise<RewriteRequestPathResult>` | Transform the incoming request path before route resolution |
| `rewriteDestinationPath` | `(options) => Promise<string>` | Transform the output path after resolution |
| `pathPatternsWithVariations` | `string[]` | Paths that should pre-compute personalizations |
| `release` | `{ id: string }` | Content release ID to resolve against |
| `quirks` | `Quirks` | Custom quirks to inject into the visitor context |
| `defaultConsent` | `boolean` | Override the default consent setting |
| `locale` | `string` | Locale to use for route resolution |
| `dataClient` | `DataClient` | Custom data client (see `references/advanced.md`) |
## Setting quirks in middleware
Inject custom quirks based on request data:
```ts
export default (request: NextRequest) => {
return handleUniformRoute({
request,
quirks: {
browser: request.headers.get("user-agent")?.includes("Chrome")
? "chrome"
: "other",
},
});
};
```
## Locale handling
Prepend locale information with `rewriteRequestPath`:
```ts
import { uniformMiddleware } from "@uniformdev/next-app-router/middleware";
const locales = ["en", "fr", "de"];
const defaultLocale = "en";
export default uniformMiddleware({
rewriteRequestPath: async ({ url }) => {
const [firstSegment] = url.pathname.split("/").filter(Boolean);
const hasLocale = firstSegment && locales.includes(firstSegment);
return {
path: hasLocale ? url.pathname : `/${defaultLocale}${url.pathname}`,
};
},
});
```
Or specify the locale directly (e.g. from a cookie or header):
```ts
export default (request: NextRequest) => {
const locale = request.cookies.get("CUSTOM_LOCALE")?.value || "en";
return handleUniformRoute({ request, locale });
};
```
## Custom route mapping
Use `findRouteMatch` to map dynamic URL patterns to Uniform project map nodes. The `keys` object passes dynamic URL segments as dynamic inputs, accessible in components via `context.dynamicInputs`:
```ts
import { handleUniformRoute } from "@uniformdev/next-app-router/middleware";
import { findRouteMatch, type CustomRoute } from "@uniformdev/next-app-router";
import { NextRequest } from "next/server";
const customRoutes: CustomRoute[] = [
{ id: "news-listing", pattern: "/news/:category" },
{ id: "product-detail", pattern: "/products/:slug" },
];
export default (request: NextRequest) => {
return handleUniformRoute({
request,
rewriteRequestPath: async ({ url }) => {
const routeMatch = findRouteMatch(customRoutes, url.pathname);
if (routeMatch?.route.id === "news-listing") {
return {
path: "/news-listing",
keys: { category: routeMatch.params.category },
};
}
if (routeMatch?.route.id === "product-detail") {
return {
path: "/product-detail",
keys: { slug: routeMatch.params.slug },
};
}
},
});
};
```
## Adding URL query string values to page state
Read query string (or other custom) values in middleware and pass them via `keys`; they become available server-side via `context.pageState.keys`. Only include keys needed to render the page:
```ts
return handleUniformRoute({
request,
rewriteRequestPath: async ({ url }) => {
const routeMatch = findRouteMatch(customRoutes, url.pathname);
if (routeMatch?.route.id === "news-listing") {
return {
path: "/news-listing",
keys: { categoryId: url.searchParams.get("categoryId") ?? "" },
};
}
},
});
```
## Serving a composition at the site root (`/`)
A Uniform project ships with a **Root** project map node at `/` that has no composition attached. To serve a page at `/`, attach your composition to that existing Root node in the Uniform Canvas UI (Project Map → Root → assign composition).
For MCP-driven workflows, note that the `mutateProjectMapNode` MCP tool is **create-only** and appends its `nodePathSegment` to the parent path — so it **cannot** rebind the pre-existing Root node. Creating a node for the home page yields `/home` (or whatever segment you pass), never `/`. Bind the root in the UI, and keep `generateStaticParams` (`createUniformStaticParams({ paths: [...] })`) in sync with the path the node actually resolves to.
## Moving the uniform page to a different location
To place the route under a different path (e.g. a locale segment), use `rewriteDestinationPath`, then move the page to `app/[locale]/uniform/[code]/page.tsx`:
```ts
export default (request: NextRequest) => {
const locale = "en";
return handleUniformRoute({
request,
rewriteDestinationPath: async (options) => {
if (options.source === "route") {
return `/${locale}/uniform/${options.code}`;
}
return `/${locale}/playground/${options.code}`;
},
});
};
```
## Scoping middleware to specific paths
Use Next.js `config.matcher` to limit which paths Uniform handles:
```ts
export const config = {
matcher: ["/", "/about", "/products/:path*"],
runtime: "experimental-edge",
};
```
## Override default consent per request
```ts
export default (request: NextRequest) => {
const hasConsent = request.cookies.get("cookie-consent")?.value === "true";
return handleUniformRoute({ request, defaultConsent: hasConsent });
};
```
## Content release support
Switch to a specific content release by passing the release ID:
```ts
export default (request: NextRequest) => {
const releaseId = request.nextUrl.searchParams.get("release");
return handleUniformRoute({
request,
release: releaseId ? { id: releaseId } : undefined,
});
};
```
SHA-256: 8de1c243d2cbde7e170dca10650397f3576a2280a92b4e6e734f3f5488c4b7c1