Next.js
Learn how to use ArkEnv in a Next.js project.
Next.js will boot a dev server and a production build without checking your schema. ArkEnv runs that check before the app serves traffic, and keeps server secrets out of Client Components.
How this host compares with Nuxt, Vite, and Bun is in Frameworks.
Quickstart
arkenv init is the default on an existing Next.js app. It detects the
framework, installs the packages, wraps next.config with withArkEnv,
gitignores .arkenv/, and writes a schema that imports from
@/.arkenv.
npx arkenv initManual installation
Already ran init?
Skip this section. The CLI already installed the packages and wrapped
next.config.
Install @arkenv/nextjs next to the engine you write schemas with, when
next.config.ts is already yours and you want each package explicit in
package.json.
ArkType engine
Install @arkenv/core and its peer dependency arktype:
npm install @arkenv/core @arkenv/nextjs arktypeStandard Schema engine
If you aren't using ArkType, install @arkenv/standard with Zod or
Valibot:
npm install @arkenv/standard @arkenv/nextjs zodnpm install @arkenv/standard @arkenv/nextjs valibot @valibot/to-json-schemaConfiguration
Next.js does not validate an env schema on its own. withArkEnv runs
that check during development and builds, and writes typed accessors to
.arkenv/env.gen.ts. Import that factory as @/.arkenv. Add
.arkenv/ to .gitignore so the generated files stay out of git.
import type { NextConfig } from "next";
import { } from "@arkenv/nextjs/config";
const : NextConfig = {};
export default ();Function-form configs work too. withArkEnv awaits your factory, then applies
aliases to the resolved object (including phase-dependent options):
import type { NextConfig } from "next";
import { withArkEnv } from "@arkenv/nextjs/config";
export default withArkEnv(async (phase, { defaultConfig }): Promise<NextConfig> => ({
...defaultConfig,
reactStrictMode: phase !== "phase-test",
}));Schema
One env.ts is the schema. Client variables use the NEXT_PUBLIC_
prefix so Next can inline them. Everything else stays on the server.
Zod and Valibot use the same @/.arkenv factory. Point withArkEnv at
@arkenv/nextjs/standard/config, shown under
Standard Schema.
import from "@/.arkenv";
export const = ({
DATABASE_URL: "string",
NEXT_PUBLIC_API_URL: "string = 'https://api.example.com'",
: "'development' | 'production' | 'test' = 'development'",
});Import env anywhere in your application. When client code accesses a server key
such as DATABASE_URL, ArkEnv throws a runtime error to prevent data leaks.
If secret names or types must stay out of the client type graph as well as values, use the optional two-module recipe.
Standard Schema
Zod and Valibot both import withArkEnv from
@arkenv/nextjs/standard/config. The schema itself stays on
@/.arkenv, as in the tabs above.
@arkenv/nextjs/standard binds the root @arkenv/standard engine and
publishes the same react-server condition as .. Server Components
and Route Handlers resolve the full schema. Client code resolves public
keys only. Zod 4.2+ coerces numbers and booleans from that entry.
Valibot does not: there is no @arkenv/nextjs/standard/valibot binding,
so installing @valibot/to-json-schema does not turn v.number() or
v.boolean() into coerced fields here. Keep Valibot fields as strings
on this entry, or validate them with
@arkenv/standard/valibot outside the
Next.js factory.
import type { NextConfig } from "next";
import { } from "@arkenv/nextjs/standard/config";
const : NextConfig = {};
export default ();Read env in application code
Import { env } from "./env" in Server Components, route handlers, and
Client Components. One module is the typed object everywhere.
Do not read process.env directly
Reading process.env skips ArkEnv. Import { env } so public keys stay
typed and coerced and server secrets stay out of the client bundle.
import { env } from "../env";
export default function Page() {
return <p>{env.NEXT_PUBLIC_API_URL}</p>;
}Reading env.DATABASE_URL from a Client Component throws. See
Client vs. server.