Layouts

Strict layout

Get the best security in Next.js using strictly separated client and server schemas.

In this setup, you define your client, server, and shared schemas in separate files.

shared.ts
client.ts
server.ts

By importing arkenv from @/env/client in client-side code and @/env/server in server-side code, you enforce a strict boundary where server variables and schemas never leak to the browser. If @/env/server is accidentally imported in a Client Component, ArkEnv fails the build with a compilation error.

Setup

The easiest way to bootstrap the strict layout is with the ArkEnv CLI. It automatically configures @arkenv/nextjs for your existing Next.js project using the strict layout option.

npx @arkenv/cli@latest init --strict
pnpm dlx @arkenv/cli@latest init --strict
yarn dlx @arkenv/cli@latest init --strict
bunx @arkenv/cli@latest init --strict

Your schema

When bootstrapping with the CLI, it generates three separate schema files under src/env/:

By default, ArkEnv for Next.js uses an automatic code generation wrapper (withArkEnv) in your Next.js config to compile your runtimeEnv block. This is why the client-side schema imports from ./generated/env.gen rather than @arkenv/nextjs/client. To opt out, simply import from @arkenv/nextjs/client directly and provide your own runtimeEnv mapping.

src/env/internal/shared.ts
import {  } from "@arkenv/nextjs/shared";

/**
 * @internal 🛑 INTERNAL SCHEMA ONLY.
 * Do not import this directly. Import `env` from `./client` or `./server` instead.
 */
export const  = ({
	: "'development' | 'production' | 'test' = 'development'",
});
src/env/client.ts
import  from "./generated/env.gen";
import {  } from "./internal/shared";

export const  = (
	{
		: "string = 'https://api.example.com'",
	},
	{
		: [],
	},
);
src/env/server.ts
import  from "@arkenv/nextjs/server";
import {  as  } from "./client";

export const  = (
	{
		: "string",
	},
	{
		: [],
	},
);

Usage

Both env/client.ts and env/server.ts export the resolved environment as env. This lets you use consistent env.MY_VAR imports depending on where the component executes.

In Server Components / Routes

Import env from the server file. It contains all public, shared, and server-only variables:

src/app/page.ts
import {  } from "@/env/server";

export function () {
	// Access database URL and client API URL safely
	const  = .;
	const  = .;
	return `API: ${}`;
}

In Client Components

Import env from the client file (env/client.ts). The boundary protection works at two levels:

  1. TypeScript Type Safety: If you import env from the client file and try to access a server-side variable (like DATABASE_URL), you will get a TypeScript compilation error because it is not defined in the client-safe schema:
src/app/client-component.ts
import {  } from "@/env/client";

export function () {
	const  = .;
	// @ts-expect-error DATABASE_URL is not defined in client env
	const  = .DATABASE_URL;

	return `API URL: ${}`;
}
  1. Compile-Time Isolation: If you accidentally import env from the server file (env/server.ts) in a Client Component (or any file imported by one), Next.js's bundler will fail the build with a compilation error. This is because @arkenv/nextjs/server imports Next.js's native server-only package to block browser compilation of server code.