Guides: Next.js: Keep server code out of client components with the nextjs preset, which follows chains of imports from each 'use client' file.
# Next.js
> Keep server code out of client components with the nextjs preset, which follows chains of imports from each 'use client' file.
This guide keeps server-only code out of the browser in a Next.js app, with the `nextjs` preset.
## Before you start
* Read [Getting started](/archangel/start/getting-started/).
* You have a Next.js app that uses the App Router and `'use client'` files.
## What the preset checks
A file that starts with `'use client'` runs in the browser. So does each file that it reaches through runtime imports. The preset refuses a chain of imports from a client file to the package `server-only` or to a Node built-in module, such as `node:fs`.
A chain stops at a file that starts with `'use server'`. Next.js changes an import of a server action into a network call, so the code of the action does not go to the browser.
The preset has one rule, `nextjs/client-stays-client`. It counts only runtime imports, because the compiler removes an `import type`.
## Add the preset
The example app has a cart page, a client button, and a module that reads the cart from disk:
* archangel.config.ts
* tsconfig.json
* app/
* cart/
* actions.ts
* page.tsx
* components/
* cart-button.tsx
* lib/
* cart.ts
* format.ts
1. Put the preset in `extends`:
archangel.config.ts
```ts
import { defineConfig, nextjs } from '@enhance-eng/archangel';
export default defineConfig({
extends: [nextjs()],
});
```
`nextjs()` checks the whole repository. To check one app of a monorepo, give its folder: `nextjs({ root: 'apps/web' })`.
2. Check the app:
Terminal
```ansi
$ npx archangel check
error[may-not-import]: components/cart-button.tsx reaches server-only through components/cart-button.tsx → lib/format.ts → lib/cart.ts → server-only
--> components/cart-button.tsx:3:29
|
3 | import { formatTotal } from '@/lib/format';
| ^^^^^^^^^^^^^^ remove an import of the chain, or move what the file needs into code that does not reach it
|
= note: rule: nextjs/client-stays-client
= note: reason: Code that runs in the browser cannot use server-only code or Node built-in modules.
= note: see: https://nextjs.org/docs/app/getting-started/server-and-client-components
checked 6 files and 9 imports: 1 problem
For what a code means, run `archangel docs may-not-import`.
```
The button is a client component. It imports `lib/format.ts` to format the total. `format.ts` imports `lib/cart.ts` for one constant, and `cart.ts` imports `server-only`. So the button pulls server code into the browser. The error shows the full chain.
These are the files of the chain:
components/cart-button.tsx
```tsx
'use client';
import { checkout } from '@/app/cart/actions';
import { formatTotal } from '@/lib/format';
export function CartButton({ total }: { total: number }) {
return ;
}
```
lib/format.ts
```ts
import { currency } from './cart';
export function formatTotal(cents: number): string {
return `${currency} ${(cents / 100).toFixed(2)}`;
}
```
lib/cart.ts
```ts
import 'server-only';
import { readFile } from 'node:fs/promises';
export const currency = 'USD';
export async function cartTotal(): Promise {
return Number(await readFile('cart.txt', 'utf8'));
}
export async function saveCart(): Promise {}
```
The button also imports `checkout` from `app/cart/actions.ts`, which also imports `lib/cart.ts`. The rule does not refuse that chain, because `actions.ts` starts with `'use server'`.
## Fix the chain
1. Move the constant into `format.ts`, so that `format.ts` imports nothing from the server:
lib/format.ts
```ts
export const currency = 'USD';
export function formatTotal(cents: number): string {
return `${currency} ${(cents / 100).toFixed(2)}`;
}
```
2. Check again:
Terminal
```ansi
$ npx archangel check
checked 6 files and 8 imports: no problems
```
## Find a chain yourself
`archangel why` shows the shortest chain of imports from one file to a file or a package:
Terminal
```ansi
$ npx archangel why components/cart-button.tsx server-only
components/cart-button.tsx:2 imports '@/app/cart/actions'
app/cart/actions.ts:2 imports '@/lib/cart'
lib/cart.ts:1 imports 'server-only'
server-only
```
why does not know where a rule stops
`why` shows the shortest chain of all imports. This chain goes through `app/cart/actions.ts`, a `'use server'` file, so the preset rule does not refuse it. Use `why` to see how code connects. Use `archangel check` to see what a rule refuses.
## Prove the preset
Terminal
```ansi
$ npx archangel test
pass nextjs/client-stays-client accepted #1 app/example-actions.ts, app/example-page.tsx
pass nextjs/client-stays-client rejected #1 app/example-helper.ts, app/example-page.tsx
2 examples, none failed; 0 rules without a rejected example
```
The preset brings its own examples: one client page that reaches `node:fs` through a helper, and one that reaches it only through a `'use server'` file.
## Next steps
* [Presets](/archangel/reference/presets/) lists the options of `nextjs`.
* [Conditions](/archangel/reference/conditions/) tells you how to write your own rule about chains of imports, with `transitively` and `stopAt`.