Security model
Understand exactly what @arkenv/nextjs protects, what Next.js protects, and how each layout affects your leak surface.
The most important thing to understand about ArkEnv: it validates your environment variables and makes them typesafe.
Without ArkEnv, process.env.DATABASE_URL is string | undefined. With ArkEnv, it's string - and if it's missing or malformed, your build or server startup fails immediately with a clear error instead of producing a silent undefined bug somewhere downstream.
But because ArkEnv manages your secrets, you need absolute certainty about how it interacts with the Next.js bundler.
Next.js natively strips non-NEXT_PUBLIC_ values from the client bundle when you reference them directly as process.env. ArkEnv builds a secure layer on top of this:
- Build-time exclusion. The
withArkEnvplugin guarantees that only client and shared values are mapped for the browser. Server secrets are deliberately excluded, meaning they are never even shipped to the client-side bundle. - Runtime misuse guard. If you try to read a server-only key in a Client Component, the ArkEnv proxy intercepts the read and throws a loud error before returning anything.
Mental model
The Next.js bundler and ArkEnv's plugin work together to hide your values. ArkEnv validates your infrastructure and prevents runtime misuse.
Leak surface
If you have a server-only secret like DATABASE_URL, here is what is actually exposed based on the layout you choose:
| 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. Next.js handles raw
process.envreferences; ArkEnv's boundary handles theenvobject. - Flat mode exposes variable names and types (but not values) because the single schema file is imported into your client code. For 99% of applications, knowing a variable is named
DATABASE_URLis not a security risk. - If your variable names reveal sensitive infrastructure (e.g. internal hostnames or vendor names), you should use the Strict Layout. Because it uses separate files, the server schema is never imported into the client graph.
Why Flat mode relies on runtime errors
You might wonder why Flat mode allows env.DATABASE_URL to autocomplete in a Client Component, only to throw an error when you actually run the code.
This comes down to how TypeScript compiles your env.ts file. TypeScript reads the file in a single pass. Because env.ts is just one file, TypeScript cannot magically return a "server type" when imported into a Server Component and a "client type" when imported into a Client Component.
If we tried to force the types to omit server keys for the browser, we would end up breaking autocomplete for your Server Components entirely.
To give you the best Developer Experience - a single file with perfect autocomplete - we intentionally type the full schema everywhere. We then rely entirely on the runtime proxy to act as the security boundary.
This approach exactly mirrors single-file mode in @t3-oss/env-nextjs (full type inference plus a runtime proxy throw). If you require strict compile-time boundaries, both libraries recommend splitting your configuration into separate files.