Web Development28 October 2025·11 min read

Server Components vs Client Components in Next.js 15

When to use server vs client components, the use client directive, data fetching patterns, performance implications, and composition strategies in Next.js 15.

Next.jsReactServer ComponentsClient ComponentsApp RouterPerformance

The Component Model Shift

Next.js 13 introduced the App Router with React Server Components (RSC). Next.js 15 has refined this model into a mature, production-ready architecture. The fundamental shift: components are server components by default. They run on the server, have zero JavaScript bundle cost on the client, and can directly access databases, file systems, and APIs.

This is the biggest change to React development since hooks. Understanding when to use server components versus client components is now the most important architectural decision in Next.js applications.

Server Components: The Default

Every component in the App Router is a server component unless you explicitly add the "use client" directive. Server components:

Run on the server only: Their code never ships to the browser. Import a 200KB charting library in a server component and your client bundle is unaffected.
Can be async: Use async/await directly in the component body to fetch data. No useEffect, no loading states in the component itself — just await your data.
Access backend resources directly: Query your database with Drizzle, read files with fs, call internal APIs — all without exposing endpoints.
Stream to the client: React renders server components to a special format that streams to the browser, enabling progressive page loading with Suspense.

What Server Components Cannot Do

No useState or useReducer — they have no client-side state
No useEffect — they have no lifecycle in the browser
No event handlers (onClick, onChange) — they cannot respond to user interactions
No browser APIs (window, localStorage, IntersectionObserver)

Client Components: Opt-In Interactivity

Add "use client" at the top of a file to make it a client component. Client components:

Run on both server (for SSR) and client: They are server-rendered for the initial HTML, then hydrated on the client for interactivity.
Support all React hooks: useState, useEffect, useRef, custom hooks — everything works.
Handle user interaction: onClick, onSubmit, onChange — any event handler requires a client component.
Access browser APIs: localStorage, window dimensions, intersection observers, media queries.

The "use client" Directive

The directive marks a boundary. The file it appears in and everything it imports becomes part of the client bundle. This is why you should push "use client" as deep into the component tree as possible — wrap only the interactive leaf components, not entire page layouts.

Data Fetching Patterns

Server Components: Direct Data Access

In server components, fetch data directly. Call your database, call external APIs, read from the file system. The data is fetched at request time (dynamic rendering) or build time (static rendering) depending on your configuration.

Use the cache() function from React to deduplicate data fetches when multiple server components need the same data during a single render.

Client Components: API Routes or Server Actions

Client components cannot access the database directly. They fetch data through:

Server Actions: Functions marked with "use server" that client components can call directly. Next.js handles the RPC communication automatically.
API Routes: Traditional REST endpoints in the app/api directory.
Client-side fetching: SWR or React Query for data that needs to refresh on the client (real-time data, polling).

Performance Implications

The server/client split has dramatic performance implications:

Bundle size: A page with 80% server components and 20% client components ships 80% less JavaScript than the same page built entirely with client components.
Time to Interactive: Less JavaScript means faster hydration. Users can interact with the page sooner.
SEO: Server-rendered HTML is immediately available to search engine crawlers.
Streaming: Server components support Suspense streaming, showing content progressively as it becomes available.

Composing Server and Client Components

The key rule: server components can import and render client components, but client components cannot import server components. However, client components can accept server components as children via the children prop.

This pattern is called the "donut" pattern: a client component wraps around server components passed as children. The client component provides interactivity (like a collapsible panel), while the server components inside provide the data-heavy content.

Common Composition Patterns

Layout with interactive header: The layout is a server component. The header navigation with mobile menu toggle is a client component. The page content is a server component.
Data table with sorting: The data fetching and initial render is a server component. The sort controls and column resizing are client components.
Form with validation: The form layout and labels are server components. The input fields with real-time validation are client components.

Think of server components as the data backbone and client components as the interactive skin. Keep the skin thin and the backbone heavy. That is the architecture Next.js 15 rewards. Want to build with this architecture? Let us talk.

BH

The Beyond Horizon Team

We are a digital agency based in Ajmer, India, specializing in Next.js web applications, React Native mobile apps, and UI/UX design. 150+ projects delivered.

About Us →

Have a project in mind?

We build fast, SEO-ready web and mobile applications.

Get a Free Consultation