Next.js splits your React tree across two environments:
By default, all layouts and pages are Server Components. You opt into Client Components only when necessary.
Use Server Components when you need to:
// app/[id]/page.tsx (Server Component)import LikeButton from '@/app/ui/like-button'import { getPost } from '@/lib/data'export default async function Page({ params }: { params: Promise<{ id: string }> }) {const { id } = await paramsconst post = await getPost(id)return (<main><h1>{post.title}</h1><LikeButton likes={post.likes} /></main>)}
This component:
Use Client Components when you need:
useState)useEffect)onClick, onChange)localStorage, window, geolocation)'use client'import { useState } from 'react'export default function LikeButton({ likes }: { likes: number }) {const [count, setCount] = useState(likes)return (<button onClick={() => setCount(count + 1)}>❤️ {count}</button>)}
The "use client" directive defines a boundary between server and client code.
Next.js renders Server Components into a special format called the:
React Server Component Payload (RSC Payload): Think of it as a blueprint of your UI, not the UI itself.
With React Server Components, we don’t want to send unnecessary JavaScript anymore.
Before RSC, the server sent:
So instead, Next.js sends:
This lets React rebuild the app without shipping server-only code to the browser.
The browser receives:
This process is called hydration — attaching event handlers to static HTML.
Server Components are never hydrated. They never run in the browser.
Next.js:
A major benefit of Server Components is sending less JS.
// layout.tsx (Server)import Search from './search'export default function Layout({ children }: { children: React.ReactNode }) {return (<><Logo /><Search />{children}</>)}
// search.tsx (Client)'use client'export default function Search() {// interactive logic}
Only the search bar ships JavaScript.
You can pass props from Server Components to Client Components:
// Server<LikeButton likes={post.likes} />
Props must be serializable (JSON-safe).
You cannot pass:
You can nest Server Components inside Client Components using children.
// modal.tsx'use client'export default function Modal({ children }: { children: React.ReactNode }) {return <div className="modal">{children}</div>}
// page.tsx (Server)import Modal from './ui/modal'import Cart from './ui/cart'export default function Page() {return (<Modal><Cart /></Modal>)}
Server content is rendered first, then inserted into the interactive shell.
React Context is not supported in Server Components.
Create providers as Client Components:
'use client'import { createContext } from 'react'export const ThemeContext = createContext('dark')export default function ThemeProvider({ children }: { children: React.ReactNode }) {return (<ThemeContext.Provider value="dark">{children}</ThemeContext.Provider>)}
Import them inside a Server layout to bridge both worlds.
import ThemeProvider from './theme-provider'export default function RootLayout({children,}: {children: React.ReactNode}) {return (<html><body><ThemeProvider>{children}</ThemeProvider></body></html>)}
React.cachePrevent duplicate fetching across Server + Client:
import { cache } from 'react'export const getUser = cache(async () => {const res = await fetch('https://api.example.com/user')return res.json()})
Multiple calls during the same request reuse the same result.