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/webis 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 allowlistedOrigin. - 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_proxiesso 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 inserver.trusted_proxies; a forgedX-Forwarded-Forotherwise reached the API through the/apirewrite (WP-70g).