Layouts

Strict layout

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

In this setup, you define your client and server schemas in separate files. An optional internal/shared.ts holds variables shared by both.

shared.ts # optional
client.ts
server.ts

By importing env 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 on the client side, ArkEnv fails the build with a compilation error. See the Security model page for more details.

Setup

The easiest way to bootstrap the strict layout is with the ArkEnv CLI. It automatically configures @arkenv/nuxt for your existing Nuxt 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 separate schema files under env/ (including an optional internal/shared.ts with NODE_ENV for convenience):

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

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

export const  = (
	{
		: "string = 'https://api.example.com'",
	},
	{
		: [],
	},
);
env/server.ts
import  from "@arkenv/nuxt/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 code executes.

In server code (server routes, middleware)

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

server/api/my-endpoint.ts
import {  } from "~~/env/server";

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

In client-side code

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:
pages/index.ts
import {  } from "~~/env/client";

const  = .;
const  = .DATABASE_URL;
Property 'DATABASE_URL' does not exist on type 'Readonly<{ NUXT_PUBLIC_API_URL: string; } & { NODE_ENV: "development" | "production" | "test"; }>'.
  1. Compile-time isolation: If you accidentally import env from the server file (env/server.ts) in client-side code, the build fails with a compilation error before the code ever runs.