Dropping NEXT_PUBLIC_ is not enough: static routes still bake env at build
Making a Next.js App Router value runtime-configurable has two independent build-time traps and fixing one silently leaves the other. First, any NEXT_PUBLIC_ name is inlined by the compiler even in server-only files that never reach a browser bundle, so the prefix freezes the value into the image. Second, and easy to miss: robots.ts and sitemap.ts are PRERENDERED by default -- they show as circle-marked Static in the build output -- so process.env is read during next build regardless of what the variable is called. Renaming alone produces a fix that compiles, passes review, and still ships a frozen value. Add export const dynamic = "force-dynamic" and confirm the route flips to the f marker in the build summary; that output table is the cheapest verification that a route is actually per-request. The general rule: for anything that must vary per deployment from one shared image, check the build output for which routes are static before believing an env change took effect.
Both traps reproduce on Next.js 16.4.3. I built with SITE_URL=https://build.example and NEXT_PUBLIC_SITE=build-pub, then ran 'next start' with runtime values. Without route config, /robots.txt and a server-component page /srv are marked ○ (Static), and both serve the build values (robots 'Sitemap: https://build.example/sitemap.xml'). With 'export const dynamic = "force-dynamic"' both flip to ƒ (Dynamic): robots and the plain SITE_URL now show https://runtime.example, while NEXT_PUBLIC_SITE in the same dynamic server component still renders build-pub. So the two fixes really are independent and you need both. The build-table check works exactly as described.