Next.js Cheatsheet
Server and Client Components
Use this Next.js reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
The Default: Server Components
Every component in app/ is a React Server Component (RSC) by default. They run on the server only — never shipped to the browser.
// app/page.tsx — Server Component (default, no directive needed) async function fetchUser(id: string) { const res = await fetch(`https://api.example.com/users/${id}`) return res.json() } export default async function ProfilePage() { const user = await fetchUser('42') // direct async/await — no useEffect return <h1>Hello, {user.name}</h1> }
What you can do in Server Components:
- async/await at the top level
- Direct DB queries, filesystem reads, secret env vars
- Import server-only modules (no bundle impact)
What you CANNOT do:
- useState, useReducer, useEffect, any React hook
- Browser APIs (window, document, localStorage)
- Event handlers (onClick, onChange, etc.)
Client Components — 'use client'
Add 'use client' at the top of the file (before imports) to opt into the client bundle.
'use client' import { useState } from 'react' export function Counter() { const [count, setCount] = useState(0) return ( <button onClick={() => setCount(c => c + 1)}> Count: {count} </button> ) }
'use client'marks a boundary. Every component imported below it in the tree is also a client component.
Boundary Propagation Rules
| Location | Behavior |
|---|---|
| No directive | Server Component |
'use client' at top | Client Component; everything it imports is also client |
| Server component imported inside a client component | Not allowed — becomes a client component too |
Client component passed as children prop to a server component | Allowed — the client component stays client-side |
Passing Server Data to Client Components
// app/page.tsx (Server Component) import { ClientWidget } from '@/components/client-widget' export default async function Page() { const data = await db.query('SELECT ...') // server-only return <ClientWidget items={data} /> // data serialized as props }
// components/client-widget.tsx (Client Component) 'use client' export function ClientWidget({ items }: { items: Item[] }) { const [selected, setSelected] = useState<Item | null>(null) // ... }
Interleaving: children Pattern
Pass a Client Component as a child of a Server Component so the server component can still fetch data.
// components/shell.tsx (Client Component) 'use client' export function Shell({ children }: { children: React.ReactNode }) { const [open, setOpen] = useState(false) return ( <div> <button onClick={() => setOpen(o => !o)}>Toggle</button> {open && children} </div> ) }
// app/page.tsx (Server Component) import { Shell } from '@/components/shell' export default async function Page() { const data = await fetchExpensiveData() // runs on server return ( <Shell> <ServerContent data={data} /> {/* also a server component */} </Shell> ) }
server-only and client-only Packages
Prevent accidental import of server or client code in the wrong environment:
npm install server-only client-only
// lib/db.ts import 'server-only' // throws a build error if imported in a client component export const db = createDb(process.env.DATABASE_URL!)
// lib/analytics.ts import 'client-only' // throws if imported in a server component export function trackEvent(name: string) { /* ... */ }
Context in Client Components
Context works normally within client component trees:
'use client' import { createContext, useContext, useState } from 'react' const ThemeContext = createContext<'light' | 'dark'>('light') export function ThemeProvider({ children }: { children: React.ReactNode }) { const [theme, setTheme] = useState<'light' | 'dark'>('light') return ( <ThemeContext.Provider value={theme}> {children} </ThemeContext.Provider> ) } export const useTheme = () => useContext(ThemeContext)
Wrap at the lowest possible level — place the Provider in a client component that wraps only what needs it:
// app/layout.tsx (Server Component — wraps with a client provider) import { ThemeProvider } from '@/components/theme-provider' export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html> <body> <ThemeProvider>{children}</ThemeProvider> </body> </html> ) }
Third-Party Components
Libraries that use browser APIs often lack 'use client'. Wrap them:
// components/map-wrapper.tsx 'use client' import dynamic from 'next/dynamic' const LeafletMap = dynamic(() => import('react-leaflet').then(m => m.MapContainer), { ssr: false, }) export function MapWrapper(props: MapProps) { return <LeafletMap {...props} /> }
Comparison Table
| Feature | Server Component | Client Component |
|---|---|---|
| Directive | (none) | 'use client' |
async/await | Yes | No (use useEffect / SWR) |
| React hooks | No | Yes |
| Browser APIs | No | Yes |
| DB / secrets | Yes | No |
| Bundle size impact | None | Yes |
| Rendered where | Server only | Server + hydrated client |
Default in app/ | Yes | No |
'use cache' Directive (Cache Components, Next 16 beta)
The 'use cache' directive (not a function) caches a file, component, or function. Enable Cache Components in config first:
// next.config.ts const config: NextConfig = { cacheComponents: true }
// Function-level: cache one async function import { cacheLife, cacheTag } from 'next/cache' export async function getUser(id: string) { 'use cache' cacheLife('hours') // profile: 'seconds' | 'minutes' | 'hours' | 'days' | 'weeks' | 'max' cacheTag(`user-${id}`) // tag for targeted invalidation return db.users.findUnique({ where: { id } }) }
// Component-level: cache a Server Component's rendered output export async function ProductCard({ id }: { id: string }) { 'use cache' const product = await fetchProduct(id) return <article>{product.name}</article> }
// File-level: first line of the file — everything exported is cached 'use cache'
Invalidate by tag from a Server Action or Route Handler:
import { revalidateTag } from 'next/cache' revalidateTag('user-42')
Do not confuse this with
unstable_cache()fromnext/cache— that is the older wrapper-function API.'use cache'+cacheLife/cacheTagis the Next 15/16 model.
Serialization Gotchas
Props passed from Server → Client Components must be serializable by React (React 19 RSC serialization is richer than JSON):
- Allowed: strings, numbers,
BigInt, booleans,undefined,null,NaN/Infinity, plain objects, arrays,Date,Map,Set,TypedArray/ArrayBuffer,FormData, JSX elements, Promises (stream to the client; unwrap with React'suse()), and Server Functions ('use server') - Not allowed: regular functions/closures, class instances, symbols (except
Symbol.for(...))