App Router: context approach
The context example wraps the app in PublicEnvProvider and reads public values in a client component with useEnvContext(). There is no script tag and no window.__ENV.
Files
| File | What it does |
|---|---|
src/app/layout.tsx | Wraps the page content in <PublicEnvProvider> |
src/instrumentation.ts | Calls makeEnvPublic('BAZ') at server start, so BAZ is also served as NEXT_PUBLIC_BAZ |
src/app/client-side/page.tsx | Client component that destructures NEXT_PUBLIC_FOO and NEXT_PUBLIC_BAZ from the hook |
src/app/server-side/page.tsx | Server component that awaits connection() and reads BAR and BAZ with env() |
src/app/api/bar-baz/route.ts | Route handler that returns BAR and BAZ as JSON |
next.config.ts | Default config; nothing next-dynenv specific |
The layout
// src/app/layout.tsx
import { PublicEnvProvider } from 'next-dynenv'
import type { ReactNode } from 'react'
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
<body>
<main>
<PublicEnvProvider>{children}</PublicEnvProvider>
</main>
</body>
</html>
)
}PublicEnvProvider is an async server component. It calls connection(), collects every NEXT_PUBLIC_* variable for the current request, and passes them to a client-side context provider. Every route under this layout renders per request.
Reading values in a client component
// src/app/client-side/page.tsx
'use client'
import { useEnvContext } from 'next-dynenv'
export default function ClientSide() {
const { NEXT_PUBLIC_FOO, NEXT_PUBLIC_BAZ } = useEnvContext()
return (
<dl>
<dt>NEXT_PUBLIC_FOO</dt>
<dd>{NEXT_PUBLIC_FOO ?? 'unset'}</dd>
<dt>NEXT_PUBLIC_BAZ</dt>
<dd>{NEXT_PUBLIC_BAZ ?? 'unset'}</dd>
</dl>
)
}Every key is typed string | undefined, so keep a fallback. useEnvContext() throws if no provider sits above the component.
env() does not see the context
In the browser, env() and envParsers read window.__ENV, which only PublicEnvScript or EnvScript create. In this app they return undefined (or your default) in client components. Use the hook, or add PublicEnvScript if you also need values outside React.
Run it
From the repository root:
pnpm install
pnpm build
cp examples/with-app-router-context/.env.example examples/with-app-router-context/.env.local
pnpm --filter example-app-router-context devThen open http://localhost:3000/client-side, /server-side, and /api/bar-baz.
Things to try
Build once, start twice.
pnpm --filter example-app-router-context build
NEXT_PUBLIC_FOO=first BAR=secret BAZ=shared pnpm --filter example-app-router-context start
NEXT_PUBLIC_FOO=second BAR=rotated BAZ=moved pnpm --filter example-app-router-context startFind the values in the HTML. View the page source and search for NEXT_PUBLIC_FOO. The values travel inside the React Server Components payload instead of a script tag, so they are still public. BAR never appears.
Remove the provider. Take PublicEnvProvider out of the layout and /client-side fails with useEnvContext must be used within a EnvProvider or PublicEnvProvider.