Middleware configuration

Developer preview

This feature is in developer preview. Use with caution as it may change unexpectedly. For more information, contact us.

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

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.

OptionTypeDescription
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.
queryStringsRecord<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.
quirksQuirksQuirks to add. They replace the Vercel geolocation quirks that have the same name.
defaultConsentbooleanThe default consent for this request. The default comes from the server configuration.
localestringThe 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.

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

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.

middleware.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.

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

middleware.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.

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

middleware.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.

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

middleware.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.

Use the matcher of the middleware configuration:

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

middleware.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).

middleware.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.