Module Boundaries with server-only and client-only
Prevent server secrets and heavy code from leaking into client bundles using poison-pill packages.
Why Module Boundaries Matter
Next.js 15 runs JavaScript in two distinct environments: the Node.js server and the browser client. Code you write in app/ can silently end up in either bundle depending on how it is imported.
Without explicit boundaries, a single careless import chain can expose:
- Database credentials and API keys stored in environment variables
- Server-only business logic that should never reach users
- Heavy Node.js libraries (crypto, fs, net) that inflate the client bundle
The poison-pill packages server-only and client-only are the idiomatic Next.js solution. They throw a build-time error the moment a module crosses the wrong boundary — catching the mistake before any code ships to production.
Installing the Packages
Both packages are published by the Next.js team and carry zero runtime code. Their entire purpose is to act as a sentinel import that the bundler recognises.
Install them once in your project:
npm install server-only client-onlyThey are tiny — each package contains only a single index.js that throws an error if evaluated in the wrong environment. The build pipeline (via React's bundler conditions) ensures the error fires at compile time, not at runtime.
You do not need to configure anything in next.config.ts. The packages rely on the react-server export condition that Next.js sets automatically.
All lessons in this course
- Analyzing and Shrinking the Client Bundle
- Turbopack and Compiler Configuration Deep Dive
- Module Boundaries with server-only and client-only
- Dynamic Imports, Code Splitting, and Lazy Hydration