Next.js cookbook · Beginner

Keep secrets and server code out of the client bundle

Make sure API keys and database code never ship to the browser in a Next.js app.

Approach

Only variables prefixed NEXT_PUBLIC_ are exposed to the browser; keep everything else unprefixed. Mark server modules with import "server-only" so importing them from a Client Component fails the build, and pass only the data the client needs (DTOs) from Server to Client Components.

Solution

// lib/db.ts
import "server-only";
import { PrismaClient } from "@prisma/client";
export const db = new PrismaClient();

// lib/stripe.ts
import "server-only";
import Stripe from "stripe";
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

// app/account/page.tsx (Server Component)
export default async function AccountPage() {
  const session = await verifySession();
  const user = await db.user.findUniqueOrThrow({ where: { id: session.userId } });
  // FIX: pass a minimal DTO, not the full record (which may include passwordHash, tokens…)
  return <ProfileForm user={{ name: user.name, email: user.email }} />;
}
# .env.local
DATABASE_URL=postgres://...            # server only
STRIPE_SECRET_KEY=sk_live_...          # server only
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_...   # intentionally public

Pitfalls

  • Prefixing secrets with NEXT_PUBLIC_ to 'make it work'.
  • Passing whole database objects as props to Client Components (serialized into the page).
  • Logging environment variables in build output.

Variations

  • React's experimental_taintObjectReference/taintUniqueValue to block specific objects/values from reaching the client.

Further reading: Next.js: Environment variables

Reference: https://nextjs.org/docs/app/guides/environment-variables

More Next.js recipes