Security model
Understand how @arkenv/nuxt protects secrets, integrates with Nuxt's runtimeConfig, and enforces strict client-server boundaries.
The primary goal of ArkEnv: validate your environment variables and make them typesafe.
In Nuxt, environment variables are dynamic. Instead of statically inlining values during compilation (like Next.js), Nuxt uses a dynamic runtimeConfig model. ArkEnv builds on top of this native architectural pattern to ensure robust safety:
- Integration with
runtimeConfig. ArkEnv registers client-side and shared variables inruntimeConfig.public(making them accessible everywhere) and server-only variables in the privateruntimeConfig(ensuring they remain strictly on the server). During production builds, server-only keys are registered with empty defaults ("") so that sensitive build-time environment values are never serialized or baked into the Nitro server bundle output. - Runtime misuse guard. In the flat layout, if a client component tries to access a server-only environment variable, the ArkEnv runtime proxy intercepts the access and throws a loud error before returning any values.
SSR Runtime Guard Caveat
The flat layout's runtime proxy only throws errors in the browser. During Server-Side Rendering (SSR), client-shared code executes on the server first. During this server execution, createEnv treats the context as the server and will return the server-only values. If you need compile-time protection against server variables being accessed in shared code during SSR, use the Strict layout.
- Compile-time boundary. In the strict layout, a custom Vite compiler plugin automatically blocks any client-side imports of server environment files (like
~/env/server.tsor@arkenv/nuxt/server), failing the build immediately before a bundle is produced.
Leak surface
Depending on the layout you choose, here is what is actually exposed in the client-side bundle:
| Layout | Secret value in client bundle | Variable name + type in client bundle | Compile-time block | Runtime block (Proxy) |
|---|---|---|---|---|
| Flat (recommended) | ❌ | ✅ | ❌ | ✅ |
| Strict (split) | ❌ | ❌ | ✅ | ✅ |
- Values are never leaked. Nuxt handles the actual environment injection dynamically at runtime, so raw values are never bundled.
- Flat layout exposes variable names and types because the single
env.tsfile containing the schema is referenced on the client. For most apps, exposing names likeDATABASE_URLis completely harmless. - Strict layout achieves absolute isolation. Since server-only schemas live in separate files (e.g.,
env/server.ts), they are never imported in client files. The Vite compiler plugin enforces this by failing the build if a client-side module imports your server schema.
How compile-time boundaries work
When using the Strict layout, the @arkenv/nuxt module injects a custom Vite plugin during build time. If Vite detects that a client-side component or helper attempts to import:
- The server-only runtime package
@arkenv/nuxt/server - Any userland file ending with
/serverinside your schema directory (e.g.~/env/server.ts)
It will halt the compiler and output a build-time error:
[ArkEnv] Importing server-only environment schema on the client is not allowed!This acts as a bulletproof double-lock, protecting your server schemas from ever leaking into client bundles.