Docs menu

Prefer static over dynamic public env vars in SvelteKit

INFO Frame app-correctness/prefer-static-public
On this page

Surface INFO-level findings on any $env/dynamic/public import. For build-time-known values, $env/static/public is safer: inlined at build, undefined imports return undefined cleanly, no runtime crash on missing env.

Companion to app-correctness/dynamic-env-declared:

Frame Severity Catches
dynamic-env-declared BLOCK THE BUG - env.PUBLIC_X referenced when PUBLIC_X isn’t declared anywhere outside local .env (will crash prod)
prefer-static-public INFO THE PATTERN - dynamic-public used at all (consider switching to static-public)

Both can fire on the same line. The first prevents an outage; the second is a hint that the dynamic indirection probably isn’t earning its keep.

When dynamic-public IS the right answer#

You genuinely need to change the value without a redeploy:

  • An A/B-test flag toggled from a control plane
  • A feature flag that flips on a fixed date
  • A per-environment endpoint that the dashboard owns

Anything else - API URL, feature defaults, version strings, third-party site IDs - is build-time-known and should use static-public.

Fix#

// BEFORE (dynamic - value read at runtime)
import { env } from '$env/dynamic/public';
const url = env.PUBLIC_API_URL;

// AFTER (static - inlined at build time)
import { PUBLIC_API_URL } from '$env/static/public';
const url = PUBLIC_API_URL;

Note: $env/static/public requires the var to be set at build time. CF Pages dashboard env vars are NOT set at build time - they’re set at runtime. If you need a dashboard-set value, dynamic-public is the right choice - accept the INFO and run nimblegate incident new if it surprises you.

Suppressing intentional cases#

// appframes:disable-next-line app-correctness/prefer-static-public
import { env } from '$env/dynamic/public';

Or whole-file:

// appframes:disable app-correctness/prefer-static-public
import { env } from '$env/dynamic/public';
// ...

Generalizes to#

Any framework with a build-time vs runtime env distinction:

  • Next.js process.env.NEXT_PUBLIC_* (build-time replaced) vs runtime config files
  • Astro import.meta.env.PUBLIC_* (build) vs runtime side-channels
  • Vite’s import.meta.env (build) vs process.env (runtime in Node adapter)

The frame currently surfaces only the SvelteKit shape; adding the others is straightforward but each needs the corresponding “static vs dynamic” import pattern.

INFO only records the finding. Turn frames on per repo on the dashboard's Policy page - see choosing what the gate checks.

Source on GitHub Live demo How it works Questions: contact@nimblegate.com