Skip to content
CommonLayer
Documentation menu
On this page

ADR-0012: Next.js App Router served same-origin with the API

Status: Accepted (plan decision D12). Implemented: shell (WP-30) and every portal page (WP-31 to WP-33, WP-70e) (amended 2026-11-10).

Context

The portal needs server-side rendering for public, indexable pages and first-party cookies for sessions. Cross-origin API calls would need CORS with credentials and make CSRF protection harder.

Decision

  • apps/web is a Next.js App Router application. The browser calls /api/v1/* on the site's own origin: Next.js rewrites to the API in development, ingress routing does the same in production.
  • Server components call the API at API_INTERNAL_URL; with time-based revalidation the call is anonymous and cacheable, without it cookies are forwarded and nothing is cached.

Consequences

  • Session cookies stay first-party and SameSite=Lax; the CSRF check can require an allowlisted Origin.
  • A nonce-based Content Security Policy requires per-request rendering, so every page is dynamic; caching happens at the data layer (WP-30 decision).
  • Next.js 16 renamed the middleware file convention: the request proxy is apps/web/src/proxy.ts.
  • The web server must be listed in server.trusted_proxies so that forwarded client IPs count for anonymous rate limits.

Amendment, 2026-11-10

  • The last bullet above is superseded by ADR-0040: the web server is not trusted by default. It forwards a visitor's address only when it is declared to run behind a trusted proxy (BEHIND_TRUSTED_PROXY), and the API trusts it only when it is listed in server.trusted_proxies; a forged X-Forwarded-For otherwise reached the API through the /api rewrite (WP-70g).