Skip to main content

Frontend

The Evolve storefront is a Next.js 15 application using the App Router, React 19, Tailwind CSS 4, and TypeScript. It is designed for fast anonymous page loads through aggressive CDN caching while providing full server-side rendering for authenticated sessions.

Rendering strategy

Visitor typeBehaviour
AnonymousCDN cache: s-maxage=900; stale-while-revalidate=2592000 (15 min fresh, 30 days stale)
AuthenticatedSSR on every request (no CDN cache)

The next.config.ts conditionally sets the Cache-Control header based on the absence of userToken and userData cookies, so returning customers always see fresh data.

Internationalization

Routing is handled by next-intl with the always prefix strategy, so every URL starts with a locale segment. Eight locales are supported:

LocaleURL prefix
nl-NL/nl
en-GB/en
de-DE/de
fr-FR/fr
es-ES/es
it-IT/it
nl-BE/be/nl
fr-BE/be/fr

Pathnames are localized per language (e.g. /nl/winkelwagen, /de/warenkorb, /en/cart). The mapping is defined in packages/site-config/src/i18n/config.ts.

Rewrites

The root /:locale path and catch-all fallback both rewrite to /content/* routes, letting the CMS handle homepage and content pages without a separate routing layer. This is configured in next-rewrites.ts.

CMS abstraction

The EVOLVE_CMS environment variable ("contentful" or "storyblok") controls which preview provider and data-attribute helper is active. This allows the same codebase to work with either CMS without build-time branching. Preview API routes are mounted at /api/preview (Storyblok) and /api/preview-contentful (Contentful).

Image optimization

A custom image loader (src/lib/image-loader.ts) detects the CDN origin from the image URL and applies the correct transform syntax:

SourceTransform example
Storyblok/m/{width}x0/filters:quality(80)
Amplience&w={width}
Contentful?w={width}&fm=webp&q=80
Lab Digital DAM?format=auto&width={width}
Bluestone PIM?format=auto&width={width}

Deployment

The application builds with output: "standalone" for containerized deployments without node_modules. The build ID is set from SERVICE_VERSION for deterministic cache busting.

A Sentry tunnel at /api/capture-errors proxies browser error reports through the application's own domain to avoid ad-blocker interference. See Observability for the full error tracking setup.