Skip to main content

Next.js middleware

The storefront middleware (src/middleware.ts) runs on every navigation request and performs four tasks in sequence: token refresh, B2B route rewriting, internationalization, and security headers.

Token refresh

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.

B2B routing

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.

Internationalization

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.

Security headers

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

HeaderValue
Strict-Transport-Securitymax-age=63072000; includeSubDomains; preload
X-Content-Type-Optionsnosniff
Referrer-Policysame-origin
Permissions-PolicyBlocks camera, microphone, geolocation
Content-Security-PolicyPer-source allowlists (see below)

Content Security Policy

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.

Matcher

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

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