Environment Variables
TanStack Start has two env var boundaries: VITE_ prefixed vars are baked into the client bundle at build time. Everything else is server-only.
Where config lives
Cloudflare Workers config sources
Client variables (VITE_ prefix)
import.meta.env.VITE_APP_NAMEVariables with the VITE_ prefix are statically replaced at build time and included in the JavaScript bundle. Use for public config only — app name, public API URLs, feature flags.
claude-start-cf0.1.0https://api.example.comThese values are embedded in the JS at build time. Changing them requires a rebuild and redeploy.
Server variables (no prefix)
process.env.SECRET_API_KEY (inside createServerFn)Variables without VITE_ prefix are only accessible in server functions. The server function returns metadata about the vars — never the actual values.
not setnot setproductionThe server function only returns whether vars are set, not their values. The actual secrets never leave the server.
Security boundary
import.meta.env.SECRET_API_KEY → undefinedAttempting to access non-VITE_ variables from client code returns undefined. Vite strips them from the bundle at build time.
undefinedundefined// ❌ These return undefined on the client
import.meta.env.SECRET_API_KEY // undefined
process.env.SECRET_API_KEY // not available
// ✅ Access secrets through server functions
const getData = createServerFn().handler(async () => {
return fetch(url, { headers: { Authorization: process.env.SECRET_API_KEY } })
})
Cloudflare vars (wrangler.jsonc)
wrangler.jsonc → "vars": { ... }Non-secret config values defined in wrangler.jsonc under vars. These are plain text, committed to your repo, and available at runtime via process.env inside server functions. Use for environment labels, feature flags, region config.
productionset-via-wrangler-jsonc// wrangler.jsonc
{
"vars": {
"APP_ENVIRONMENT": "production",
"DEMO_WRANGLER_VAR": "set-via-wrangler-jsonc"
}
}These values are live — read from the Worker runtime on each request, not baked in at build time. You can also override per-environment using the Cloudflare dashboard.
Cloudflare secrets (encrypted)
wrangler secret put SECRET_NAMEFor actual secrets (API keys, database credentials, JWT secrets), use Cloudflare's encrypted secret storage. Secrets are never stored in your repo or wrangler.jsonc — they're set via the CLI or dashboard and encrypted at rest.
Setting secrets
# Set a secret interactively (prompts for value) wrangler secret put DATABASE_URL # Set from a pipe (CI/CD) echo "postgresql://..." | wrangler secret put DATABASE_URL # Set for a specific environment wrangler secret put DATABASE_URL --env staging
Managing secrets
# List all secrets (names only, values are hidden) wrangler secret list # Delete a secret wrangler secret delete DATABASE_URL
Accessing in code
// Secrets are accessed the same way as vars
const getDb = createServerFn().handler(async () => {
const url = process.env.DATABASE_URL
return db.connect(url)
})Secrets and vars merge into the same process.env namespace at runtime. The only difference is how they're stored — vars are plain text in wrangler.jsonc, secrets are encrypted on Cloudflare's infrastructure.
How process.env works on Workers
nodejs_compat → process.envCloudflare Workers don't natively have process.env. The nodejs_compat flag shims it, making wrangler vars and secrets available through the standard Node.js API.
| Source | When available | Stored in repo? |
|---|---|---|
| .dev.vars | Local dev runtime | Gitignored |
| .env | Build time only (VITE_ vars) | Gitignored |
| wrangler.jsonc vars | Dev + production runtime | Yes (plain text) |
| wrangler secret | Dev + production runtime | No (encrypted) |
| CF Dashboard | Production runtime only | No (encrypted) |
Which to use?
wrangler.jsonc vars — Non-secret config: environment labels, feature flags, public API URLs. Committed to git so the whole team shares them.
wrangler secret put — Actual secrets: database URLs, API keys, JWT signing keys. Encrypted, never in source control. Set once per environment.
CF Dashboard — Same as secrets but managed through the web UI. Good for non-developers or one-off overrides.
.dev.vars — Local dev secrets. Loaded by Miniflare during npm run dev. Not used in production.
.env — Vite build-time vars only (VITE_ prefix). Not used at runtime on Workers.
Type safety with env.d.ts
src/env.d.tsDeclare your env var types so TypeScript catches missing or mistyped variables at compile time.
// src/env.d.ts
interface ImportMetaEnv {
readonly VITE_APP_NAME: string
readonly VITE_APP_VERSION: string
readonly VITE_PUBLIC_API_URL: string
}
declare global {
namespace NodeJS {
interface ProcessEnv {
readonly SECRET_API_KEY: string
readonly DATABASE_URL: string
}
}
}With this file, import.meta.env.VITE_TYPO would be a TypeScript error. See src/env.d.ts in this project for the full declaration.