# Middleware configuration with the Next.js App Router SDK

> Configure the middleware of the Next.js App Router SDK: options, query strings, quirks, locales, route mapping, consent and releases.

Source: https://docs.uniform.app/docs/sdk/nextjs-app-router/middleware

> **Developer Preview:**
>
> This page documents the developer preview of the SDK, version `20.81.1-alpha.25.sha-f076f9b857`.

The middleware runs before each page request. It makes no network calls. It writes these values into the code:

- the route path
- the `keys` from `rewriteRequestPath`
- the composition state: published, draft or editor
- the preview mode
- the default consent
- the locale and the release
- the edge mode flag

`uniformMiddleware(options)` returns a middleware function. `handleUniformRoute({ request, ...options })` handles one request, so you can add your own logic around it.

`middleware.ts`

```ts
import { handleUniformRoute } from "@uniformdev/next-app-router/middleware";
import type { NextRequest } from "next/server";

export default function middleware(request: NextRequest) {
  return handleUniformRoute({ request });
}
```

The edge middleware (`vercelUniformEdgeMiddleware`, `uniformEdgeMiddleware`) accepts the same options.

## Middleware options

| Option | Type | Description |
| --- | --- | --- |
| `rewriteRequestPath` | `({ url, request }) => Promise<{ path, keys? } \| undefined>` | Changes the path before it goes into the code. `keys` adds values to the page state. |
| `rewriteDestinationPath` | `({ code, pageState, source }) => Promise<string>` | Changes the rewrite destination. `source` is `"route"` or `"playground"`. An empty string uses the default. |
| `queryStrings` | `Record<string, string[]>` | The query strings to keep in the route path, for each path pattern. The middleware removes all other query strings. |
| `release` | `{ id: string }` | The release to show. In draft mode, the default is the `releaseId` query string. |
| `quirks` | `Quirks` | Quirks to add. They replace the Vercel geolocation quirks that have the same name. |
| `defaultConsent` | `boolean` | The default consent for this request. The default comes from the server configuration. |
| `locale` | `string` | The locale for route resolution. |

> **Developer Preview:**
>
> The options `dataClient` and `pathPatternsWithVariations` are removed. `queryStrings` is new. The middleware no longer reads the query strings of the project map node.

## Keep query strings in the route

By default, the middleware removes the query strings from the route path. Thus `/?utm_campaign=launch` and `/` share one cached page. To keep a query string, because the route uses it, list it for its path pattern:

`middleware.ts`

```ts
import { uniformMiddleware } from "@uniformdev/next-app-router/middleware";

export default uniformMiddleware({
  queryStrings: {
    "/search": ["q", "page"],
    "/products/:slug": ["color"],
  },
});
```

Each different value of a listed query string makes a different code, and thus a different cached page. List only the query strings that change the content.

## Set quirks in the middleware

`middleware.ts`

```ts
import { handleUniformRoute } from "@uniformdev/next-app-router/middleware";
import type { NextRequest } from "next/server";

export default function middleware(request: NextRequest) {
  return handleUniformRoute({
    request,
    quirks: {
      browser: request.headers.get("user-agent")?.includes("Chrome") ? "chrome" : "other",
    },
  });
}
```

The quirks do not go into the code. The browser gets them in the `ufqc` cookie, and the edge middleware uses them to choose variants.

## Add the locale to the path

For a localized site, use `rewriteRequestPath` to add the default locale to a path that does not have one:

`middleware.ts`

```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 !== undefined && locales.includes(firstSegment);
    return { path: hasLocale ? url.pathname : `/${defaultLocale}${url.pathname}` };
  },
});
```

For more information, refer to [Localize your app](https://docs.uniform.app/docs/sdk/nextjs-app-router/localization).

## Map URLs to project map nodes

Use `findRouteMatch` to send URL patterns to a project map node, and give the dynamic segments as `keys`:

`middleware.ts`

```ts
import { findRouteMatch, type CustomRoute } from "@uniformdev/next-app-router";
import { handleUniformRoute } from "@uniformdev/next-app-router/middleware";
import type { NextRequest } from "next/server";

// The id of each route is the path of its project map node.
const customRoutes: CustomRoute[] = [
  { id: "/news-listing", pattern: "/news/:category" },
  { id: "/product-detail", pattern: "/products/:slug" },
];

export default function middleware(request: NextRequest) {
  return handleUniformRoute({
    request,
    rewriteRequestPath: async ({ url }) => {
      const match = findRouteMatch(customRoutes, url.pathname);
      // match.params is { category: "…" } or { slug: "…" }
      return match ? { path: match.route.id, keys: match.params } : undefined;
    },
  });
}
```

The `keys` go into the code. Components read them in `context.pageState.keys`. Each different value of a key makes a different code, so add only the keys that the page uses.

`findRouteMatch` supports `:name` segments only. A `:name` segment matches one path segment.

## Move the composition route

To put the composition route in another folder, for example under a locale segment, return the new path from `rewriteDestinationPath`:

`middleware.ts`

```ts
import { uniformMiddleware } from "@uniformdev/next-app-router/middleware";

export default uniformMiddleware({
  rewriteDestinationPath: async ({ code, pageState, source }) => {
    const locale = pageState.locale ?? "en";
    return source === "route" ? `/${locale}/uniform/${code}` : `/${locale}/playground/${code}`;
  },
});
```

Then put the page in `app/[locale]/uniform/[code]/page.tsx`.

## Limit the middleware to some paths

Use the `matcher` of the middleware configuration:

```ts
export const config = {
  matcher: ["/", "/about", "/products/:path*"],
  runtime: "experimental-edge",
};
```

## Set the default consent for each request

`middleware.ts`

```ts
import { handleUniformRoute } from "@uniformdev/next-app-router/middleware";
import type { NextRequest } from "next/server";

export default function middleware(request: NextRequest) {
  return handleUniformRoute({
    request,
    defaultConsent: request.cookies.get("cookie-consent")?.value === "true",
  });
}
```

The default consent goes into the code. Thus each consent value makes a different cached page. If you prebuild pages, list each value in `createUniformStaticParams`. Refer to [Static generation (ISR)](https://docs.uniform.app/docs/sdk/nextjs-app-router/incremental-static-regeneration-isr#consent-and-edge-mode-values).

## Show a release

`middleware.ts`

```ts
import { handleUniformRoute } from "@uniformdev/next-app-router/middleware";
import type { NextRequest } from "next/server";

export default function middleware(request: NextRequest) {
  const releaseId = request.nextUrl.searchParams.get("release");
  return handleUniformRoute({
    request,
    release: releaseId ? { id: releaseId } : undefined,
  });
}
```

In draft mode, the middleware also reads the `releaseId` query string. Canvas adds it when you preview a release.
