Environment variables
Environment variables in the Next.js to ShadowJS guide.
Dusk has two kinds, and the prefix is the rule.
| Next.js | Dusk |
|---|---|
NEXT_PUBLIC_API_URL, read as process.env.NEXT_PUBLIC_API_URL |
DUSK_PUBLIC_API_URL, read as import.meta.env.DUSK_PUBLIC_API_URL (or API_URL in .env.public, with no prefix). Replaced at build time |
process.env.DATABASE_URL on the server |
process.env.DATABASE_URL, validated at startup by defineEnv and read from event.env |
process.env.NODE_ENV |
import.meta.env.MODE, import.meta.env.DEV, import.meta.env.PROD |
.env, .env.local, .env.production |
Same files for server values. Values the browser may read go in .env.public (committed), which Next has no equivalent for. Dusk loads the server files into process.env in development and at build time (see below) |
Reading a secret in a client component by mistake: undefined at runtime |
Build error [ENV_NOT_PUBLIC] naming the variable |
Only the public names reach the browser: the DUSK_PUBLIC_ ones and the names declared in .env.public. A client file that
reads import.meta.env.DATABASE_URL, or VITE_X (Dusk replaces Vite's VITE_ default with DUSK_PUBLIC_), fails the
build. A public value that looks like a key or a password fails the build too ([ENV_PUBLIC_VALUE]), so a value in
.env.public is a value you would put in the browser deliberately. Server variables come from the real process
environment. In development, and while dusk build prerenders, dusk() loads .env.public, .env, .env.[mode], .env.local, .env.[mode].local and .env.public.local into process.env for server code, the later ones winning (a variable that is already set wins over all of them); in production there is no file loading, so use your host's environment settings, or node --env-file for a file next to the server. This order is Dusk's own: .env.local beats .env.[mode], which is the opposite of Vite's. Declare the server variables once with defineEnv; the handler validates them at startup and refuses to
serve a misconfigured app, naming the variables (never their values).
import { defineEnv, EnvError } from "@shadow-js/dusk/server";
export const env = defineEnv({
DATABASE_URL: { type: "url", protocols: ["postgres:"], secret: true },
PORT: { type: "number", integer: true, default: 3000 },
DUSK_PUBLIC_API_URL: { type: "url", required: false },
});
const ok = env.parse({ DATABASE_URL: "postgres://localhost/blog" });
console.log(ok.all.PORT, ok.all.DATABASE_URL);
try {
env.parse({ PORT: "3.5" });
} catch (error) {
if (error instanceof EnvError) console.log(error.message);
}
3000 postgres://localhost/blog
Invalid environment: DATABASE_URL (required), PORT (expected an integer)Pass the schema as env in src/entry-server.ts (env: env); event.env.DATABASE_URL and handler.env hold the
validated values. A client file imports nothing from here; it reads import.meta.env.DUSK_PUBLIC_API_URL, which the build
replaces. basePath becomes Vite's base; Dusk passes it to the handler and the router.