/** * Server request shell. Reads the incoming request once and does two things, * both decided by pure functions in src/domain: * * 1. Picks the language of the request and maps the public address onto the * app directory (routes.ts). * 2. Records a page view server-side (pageview-decision.ts). No tracking * script ships to the browser. * * Page views are browser navigations (`sec-fetch-dest: document`). * Router-internal headers (RSC, next-router-prefetch) are stripped by the * server before this shell runs and cannot be used here. */ import { NextResponse, type NextRequest } from "next/server"; import { recordPageviewToUmami } from "@/src/analytics/recordPageviewToUmami"; import { LOCALE_COOKIE } from "@/src/domain/locale"; import { decidePageview } from "@/src/domain/pageview-decision"; import { routeRequest } from "@/src/domain/routes"; const PERMANENT = 308; const TEMPORARY = 307; const primaryLanguage = (acceptLanguage: string | null): string => acceptLanguage?.split(",")[0]?.split(";")[0]?.trim() ?? ""; const firstForwardedIp = (forwardedFor: string | null): string => forwardedFor?.split(",")[0]?.trim() ?? ""; const hostnameWithoutPort = (host: string): string => host.replace(/:\d+$/, ""); export function proxy(request: NextRequest): NextResponse { const route = routeRequest({ pathname: request.nextUrl.pathname, cookie: request.cookies.get(LOCALE_COOKIE)?.value, acceptLanguage: request.headers.get("accept-language"), }); const url = request.nextUrl.clone(); url.pathname = route.to; const response = route.kind === "redirect" ? NextResponse.redirect(url, route.permanent ? PERMANENT : TEMPORARY) : NextResponse.rewrite(url); // The answer depends on the visitor's language: caches must keep one per language. if (route.negotiated) { response.headers.append("Vary", "Accept-Language, Cookie"); } // A redirect is not a view: the request that follows it is counted instead. if (route.kind !== "redirect") { const pageview = decidePageview({ method: request.method, secFetchDest: request.headers.get("sec-fetch-dest") ?? "", // The public address the visitor asked for, not the internal one it maps to. pathname: request.nextUrl.pathname, search: request.nextUrl.search, hostname: hostnameWithoutPort( request.headers.get("x-forwarded-host") ?? request.headers.get("host") ?? "", ), referrer: request.headers.get("referer") ?? "", language: primaryLanguage(request.headers.get("accept-language")), userAgent: request.headers.get("user-agent") ?? "", clientIp: firstForwardedIp(request.headers.get("x-forwarded-for")), }); if (pageview.track) { recordPageviewToUmami(pageview.facts); } } return response; } export const config = { // Pages only: not Next's own files, the error proxy, or files. matcher: ["/((?!_next/|errors/|.*\\..*).*)"], };