gotchatypescriptnextjsCritical
server-only package: preventing server code from leaking to client bundles
Viewed 0 times
Next.js 13+ with App Router
server-onlysecretsclient bundleNEXT_PUBLIC_securityserver import
Error Messages
Problem
A utility module with database credentials or API secrets gets imported by a Client Component — Next.js doesn't automatically prevent this, and the secrets end up in the client bundle.
Solution
Import 'server-only' at the top of any module that must never run client-side:
// lib/db.ts — MUST NOT be sent to client
import 'server-only';
import { PrismaClient } from '@prisma/client';
export const db = new PrismaClient();
// lib/config.ts — contains secrets
import 'server-only';
export const config = {
stripeSecret: process.env.STRIPE_SECRET_KEY!,
databaseUrl: process.env.DATABASE_URL!,
};
// If a Client Component tries to import lib/db.ts:
// 'use client';
// import { db } from '@/lib/db'; // BUILD ERROR
// Error: This module cannot be imported from a Client Component module.
// lib/stripe.ts — server-only module
import 'server-only';
import Stripe from 'stripe';
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
// For client-safe environment variables:
// Use NEXT_PUBLIC_ prefix (intentionally exposed):
export const PUBLIC_API_URL = process.env.NEXT_PUBLIC_API_URL;
// lib/db.ts — MUST NOT be sent to client
import 'server-only';
import { PrismaClient } from '@prisma/client';
export const db = new PrismaClient();
// lib/config.ts — contains secrets
import 'server-only';
export const config = {
stripeSecret: process.env.STRIPE_SECRET_KEY!,
databaseUrl: process.env.DATABASE_URL!,
};
// If a Client Component tries to import lib/db.ts:
// 'use client';
// import { db } from '@/lib/db'; // BUILD ERROR
// Error: This module cannot be imported from a Client Component module.
// lib/stripe.ts — server-only module
import 'server-only';
import Stripe from 'stripe';
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
// For client-safe environment variables:
// Use NEXT_PUBLIC_ prefix (intentionally exposed):
export const PUBLIC_API_URL = process.env.NEXT_PUBLIC_API_URL;
Why
Next.js can't automatically determine which code is safe to send to the client. Without 'server-only', a developer can accidentally import a server-only module from a Client Component, and Next.js will bundle it and potentially expose secrets. The 'server-only' package adds a build-time check.
Gotchas
- NEXT_PUBLIC_ env vars are intentionally inlined into the client bundle — never use this prefix for secrets
- 'server-only' is a package you must install: npm install server-only
- The inverse is 'client-only' — for modules that use browser APIs and must not run on the server
- Server Actions marked 'use server' are callable from the client but run on the server — secrets inside them are safe
Code Snippets
Protecting secret API clients with server-only
// lib/stripe.ts
import 'server-only';
import Stripe from 'stripe';
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);Context
When securing server-only modules (database clients, secret config) from accidental client import
Revisions (0)
No revisions yet.