Skip to content

New: AI agent integration via Model Context Protocol (MCP).Learn more

Next.js middleware

The storefront proxy (src/proxy.ts, Next.js 16’s successor to middleware.ts) runs on every navigation request and performs four tasks in sequence: token refresh, B2B route rewriting, internationalization, and security headers.

flowchart TD
    A[Incoming request] --> B{userToken expired<br/>AND refresh cookie exists?}
    B -- Yes --> C[Redirect to /account/refresh-session]
    B -- No --> D{hasBusinessContext?}
    D -- Yes --> E[Redirect /account/* → /account-b2b/account/*]
    D -- No --> F[next-intl locale routing]
    E --> F
    F --> G[Set security headers]
    G --> H[Response]

The middleware reads the JOSE header from the userToken cookie using jose.decodeProtectedHeader() and checks the exp claim. If the token has been expired for more than 5 minutes and a userRefreshTokenExists cookie is present, the request is redirected to /account/refresh-session which exchanges the refresh token for a new access token.

This cleans up stale sessions so server-rendered pages don’t attempt to use long-expired tokens.

When the userData JWT contains a businessContext object (checked via readUserData()), the middleware redirects /account/* requests to /account-b2b/account/*. This lets B2B customers see a different account section with company management, quotes, and approval workflows.

See B2B architecture for how business context is established.

The next-intl middleware handles locale detection and prefix routing. Every URL must include a locale segment (e.g. /en/cart, /nl/winkelwagen). The locale list and custom prefix mapping are defined in packages/site-config/src/i18n/config.ts.

The setResponseHeaders() function from src/lib/secure-headers.ts adds security headers to every response:

Header Value
Strict-Transport-Security max-age=63072000; includeSubDomains; preload
X-Content-Type-Options nosniff
Referrer-Policy same-origin
Permissions-Policy Blocks camera, microphone, geolocation
Content-Security-Policy Per-source allowlists (see below)

The CSP is assembled dynamically based on environment variables. Key source allowlists:

  • connect-src: GraphQL gateway, OpenTelemetry endpoint, Sentry, Google Analytics, Adyen
  • script-src: self, GTM, Stripe, Sentry, Storyblok bridge
  • frame-src: Stripe, YouTube, Adyen checkout
  • img-src: self, HTTPS, data URIs, Contentful assets

In production, upgrade-insecure-requests is added to the CSP directives. Sentry CSP violation reports are configured through the Report-To header.

The middleware intercepts all routes except API routes and static files (anything with a file extension):

export const config = {
matcher: ["/", "/((?!api|_next|.*\\..*).*)" ],
};