TanStack Start
Learn how to use ArkEnv in a TanStack Start project.
TanStack Start is designed to work seamlessly with typesafe env validation. TanStack Start apps use ArkEnv to validate environment variables at boot, inline public keys into the client bundle, and keep server secrets inside server functions.
When the Vite plugin is registered, it discovers and validates env.ts during
Vite config resolution. Missing or invalid values abort the dev server or
production build before it is ready, and relevant .env or schema changes are
revalidated during HMR. Without the plugin, validation is import-driven and
starts when a module first imports env.ts.
This is the existing fail-fast contract, not a new lazy-validation option. ArkEnv does not add a default-off flag for lazy validation.
For high-level architectural trade-offs, see Frameworks.
Quickstart
Vite is the default path. The TanStack CLI add-on scaffolds the Vite
plugin, an env.ts schema, and ArkType as the validator engine.
Create a new project
Generate a TanStack Start app with ArkEnv wired in:
npx @tanstack/cli create my-app --add-ons https://arkenv.js.org/tanstack/info.jsonpnpm dlx @tanstack/cli create my-app --add-ons https://arkenv.js.org/tanstack/info.jsonyarn dlx @tanstack/cli create my-app --add-ons https://arkenv.js.org/tanstack/info.jsonbunx @tanstack/cli create my-app --add-ons https://arkenv.js.org/tanstack/info.jsonThe add-on installs the validator runtime, registers
arkenvVitePlugin() in vite.config.ts, writes src/env.ts, and
pre-populates declared keys in .env.example.
Existing projects
If you already have a TanStack Start app on Vite, add ArkEnv with either the TanStack CLI add-on or the ArkEnv CLI:
npx @tanstack/cli add https://arkenv.js.org/tanstack/info.jsonpnpm dlx @tanstack/cli add https://arkenv.js.org/tanstack/info.jsonyarn dlx @tanstack/cli add https://arkenv.js.org/tanstack/info.jsonbunx @tanstack/cli add https://arkenv.js.org/tanstack/info.jsonnpx arkenv initpnpm dlx arkenv inityarn dlx arkenv initbunx arkenv initarkenv init detects @tanstack/react-start and installs
@arkenv/vite-plugin the same way it does for Vite projects.
Using Rsbuild?
ArkEnv fully supports Rsbuild via @arkenv/rsbuild-plugin.
@tanstack/cli does not currently support Rsbuild for scaffolding or
add-ons (track
TanStack/cli#505).
Configure via Manual installation and
Rsbuild.
Pinning RCs
Until GA, pin exact @arkenv/* RC versions in the lockfile if you want
bit-for-bit reproducibility across machines and CI.
Manual installation
Install the plugin that matches your bundler alongside your validation
engine. Author bare @arkenv/* package names; the docs site applies the
release tag automatically.
Prefer @arkenv/core and your host plugin as app dependencies — not
the arkenv CLI package.
Vite
ArkType engine
npm install @arkenv/core arktype
npm install -D @arkenv/vite-pluginpnpm add @arkenv/core arktype
pnpm add -D @arkenv/vite-pluginyarn add @arkenv/core arktype
yarn add --dev @arkenv/vite-pluginbun install @arkenv/core arktype
bun install --dev @arkenv/vite-pluginStandard Schema engine
If you aren't using ArkType, install @arkenv/standard:
npm install @arkenv/standard
npm install -D @arkenv/vite-pluginpnpm add @arkenv/standard
pnpm add -D @arkenv/vite-pluginyarn add @arkenv/standard
yarn add --dev @arkenv/vite-pluginbun install @arkenv/standard
bun install --dev @arkenv/vite-pluginRsbuild
ArkType engine
npm install @arkenv/core arktype
npm install -D @arkenv/rsbuild-pluginpnpm add @arkenv/core arktype
pnpm add -D @arkenv/rsbuild-pluginyarn add @arkenv/core arktype
yarn add --dev @arkenv/rsbuild-pluginbun install @arkenv/core arktype
bun install --dev @arkenv/rsbuild-pluginStandard Schema engine
If you aren't using ArkType, install @arkenv/standard:
npm install @arkenv/standard
npm install -D @arkenv/rsbuild-pluginpnpm add @arkenv/standard
pnpm add -D @arkenv/rsbuild-pluginyarn add @arkenv/standard
yarn add --dev @arkenv/rsbuild-pluginbun install @arkenv/standard
bun install --dev @arkenv/rsbuild-pluginConfiguration
Register the ArkEnv plugin next to TanStack Start and React in your bundler config.
Vite
TanStack Start's React recipe on Vite expects viteReact() after
tanstackStart() (for JSX / Fast Refresh). Add arkenvVitePlugin() to
the same plugins array:
import { tanstackStart } from "@tanstack/react-start/plugin/vite";
import viteReact from "@vitejs/plugin-react";
import arkenvVitePlugin from "@arkenv/vite-plugin";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [
tanstackStart({ srcDirectory: "src" }),
// React's Vite plugin must come after Start's plugin
viteReact(),
arkenvVitePlugin(),
],
});Install @vitejs/plugin-react if it is not already a dependency. If you
aren't using ArkType, import the ArkEnv plugin from
@arkenv/vite-plugin/standard.
React Refresh must come after Start's plugin
Place @vitejs/plugin-react (or @vitejs/plugin-react-swc) after
tanstackStart(). Without it, Vite cannot resolve /@react-refresh,
SSR still renders, and client hydration fails so handlers never attach.
Rsbuild
TanStack Start also supports Rsbuild via
@tanstack/react-start/plugin/rsbuild. Register
arkenvRsbuildPlugin() in your rsbuild.config.ts:
import { pluginReact } from "@rsbuild/plugin-react";
import { tanstackStart } from "@tanstack/react-start/plugin/rsbuild";
import { arkenvRsbuildPlugin } from "@arkenv/rsbuild-plugin";
import { defineConfig } from "@rsbuild/core";
export default defineConfig({
plugins: [
tanstackStart({ srcDirectory: "src" }),
pluginReact(),
arkenvRsbuildPlugin(),
],
});If you aren't using ArkType, import the plugin from
@arkenv/rsbuild-plugin/standard.
Define your schema
Create an env.ts file in your source tree. Keys your client bundle
reads must match your bundler's client prefix: VITE_ for Vite or
PUBLIC_ for Rsbuild. Everything else stays on the server.
Import from the runtime package, not the CLI
Runtime validation lives in @arkenv/core (ArkType) or
@arkenv/standard (Zod/Valibot). The arkenv package is the
interactive CLI only. Importing arkenv from the CLI package throws
and points you at @arkenv/core. See
init reference.
import from "@arkenv/core";
export const = ({
: "string",
: "number.port = 3000",
: "string = 'https://api.example.com'",
: "'development' | 'production' | 'test' = 'development'",
});import from "@arkenv/core";
export const = ({
: "string",
: "number.port = 3000",
: "string = 'https://api.example.com'",
: "'development' | 'production' | 'test' = 'development'",
});Pass the schema as an object literal
Pass the object literal into arkenv({ ... }), or wrap a shared shape
with type(...) from @arkenv/core. An untyped intermediate object
can break overload inference.
Server functions and client access
Read the validated env object from your schema module in routes and
server functions.
Server keys in createServerFn
Server-only keys work inside createServerFn handlers. The handler runs
on the server, where env.ts executes the real validation runtime
against your process environment:
import { createFileRoute } from "@tanstack/react-router";
import { createServerFn } from "@tanstack/react-start";
import { env } from "../env";
const getDatabaseHost = createServerFn({ method: "GET" }).handler(() => {
const url = new URL(env.DATABASE_URL); // server-only: validated at boot
return url.host; // safe to return to the client
});
export const Route = createFileRoute("/")({
component: Home,
loader: () => getDatabaseHost(),
});
function Home() {
const dbHost = Route.useLoaderData();
return (
<div>
<h1>API: {env.VITE_API_URL}</h1>
<p>Database Host: {dbHost}</p>
</div>
);
}import { createFileRoute } from "@tanstack/react-router";
import { createServerFn } from "@tanstack/react-start";
import { env } from "../env";
const getDatabaseHost = createServerFn({ method: "GET" }).handler(() => {
const url = new URL(env.DATABASE_URL); // server-only: validated at boot
return url.host; // safe to return to the client
});
export const Route = createFileRoute("/")({
component: Home,
loader: () => getDatabaseHost(),
});
function Home() {
const dbHost = Route.useLoaderData();
return (
<div>
<h1>API: {env.PUBLIC_API_URL}</h1>
<p>Database Host: {dbHost}</p>
</div>
);
}Client keys in components
Client components read public keys (VITE_* on Vite, PUBLIC_* on
Rsbuild) from the same import. The plugin rewrites the client module so
these values are inlined as coerced literals.
Use the env import, not import.meta.env
Reading import.meta.env skips ArkEnv. Import { env } so public keys
stay typed and coerced and server secrets stay out of the client
bundle.
Reading a server-only key in the browser throws instead of leaking:
env.VITE_API_URL; // string, inlined into the client bundle
env.DATABASE_URL; // throws in the browser; real value on the serverenv.PUBLIC_API_URL; // string, inlined into the client bundle
env.DATABASE_URL; // throws in the browser; real value on the serverSSR and the client boundary
TanStack Start builds two module graphs: the SSR graph for the server functions and routes you render on the server, and the client graph for what ships to the browser.
- Server graph:
env.tsexecutes the real@arkenv/coreruntime at boot and validates your process environment before requests are served.createServerFnhandlers run on this graph, so they read real, validated values. - Client graph:
@arkenv/vite-pluginor@arkenv/rsbuild-plugintransformsenv.tsduring the client build. It inlines public values and replaces private server keys with throwing getters, so the validator engine never ships to the browser.
Like Vite and Rsbuild, TanStack Start loads .env* files in
development. In production, the environment comes from the process that
starts your server, so containers must inject variables before boot.
Examples
Run a complete setup end to end:
with-tanstack-start: TanStack Start with Vite and@arkenv/vite-plugin.with-tanstack-start-rsbuild: TanStack Start with Rsbuild and@arkenv/rsbuild-plugin.
Both examples demonstrate server-only DATABASE_URL accessed inside
createServerFn, public keys rendered in a client component, and a
button that demonstrates the client-side throw.
Next steps
@arkenv/vite-plugin reference
Transform modes, configuration options, and Vite plugin exports.
@arkenv/rsbuild-plugin reference
Transform modes, configuration options, and Rsbuild plugin exports.
Client vs. server
Understand how ArkEnv protects server keys in client bundles.
Hosting presets
Inject hosting provider system variables into your TanStack Start schema.