Reducing Client Bundle Size with Next.js Server Components Data Fetching
Learn how to move data fetching into Next.js Server Components to shrink the client‑side JavaScript bundle, keep interactivity isolated, and verify the improvement with browser tools.
16 Feb 2026, 12:23 UTC

When a React app fetches data on the client, the browser must download the fetching logic, any request library, and state‑management code before it can render the UI. This adds weight to the JavaScript bundle and delays Time to Interactive, especially on slower networks.
Move Fetching to the Server
In the Next.js App Router, a component is a Server Component by default. Because it runs on the server, you can use standard async/await to fetch data. The server resolves the promise, renders the resulting HTML, and sends only that markup to the browser. The fetch function and any libraries used inside it never travel to the client.
Worked Example: Product List
// app/products/page.js (Server Component by default)
async function getProducts() {
const res = await fetch('https://api.example.com/products', {
next: { revalidate: 3600 } // cache for 1 hour
});
if (!res.ok) throw new Error('Failed to fetch');
return res.json();
}
export default async function ProductsPage() {
const products = await getProducts();
return (
Our Products
{products.map(p => (
-
{p.name} – ${p.price}
))}
);
}
In this example, the getProducts function and the fetch call are executed exclusively on the server. The browser receives only the generated HTML list; no JavaScript for the request or JSON parsing is included in the initial payload.
Keeping the Interactive Boundary Small
Server Components cannot hold state or respond to user actions. For interactive elements such as an “Add to Cart” button, create a Client Component and nest it inside the Server Component.
// components/AddToCartButton.js
'use client';
import { useState } from 'react';
export default function AddToCartButton({ productId }) {
const [qty, setQty] = useState(0);
return (
setQty(qty + 1)}>
Add to cart ({qty})
);
}
By placing <AddToCartButton productId={p.id} /> inside the ProductsPage map, the heavy data‑fetching stays on the server while only the tiny button logic runs in the browser.
Limitations and How to Verify
The main constraint is serialization: props passed from a Server Component to a Client Component must be JSON‑serializable. You cannot send functions, class instances, or circular objects. If you try, Next.js will throw an error during rendering.
Another pitfall is a request waterfall. If a Server Component awaits data and then renders another Server Component that also awaits, the second fetch starts only after the first finishes. To fetch independent data in parallel, wrap the calls in Promise.all().
To confirm that the client bundle is smaller, open the browser’s Network tab, reload the page, and look at the JavaScript chunks. Verify that libraries used only inside getProducts (e.g., if you imported a heavy server‑only SDK) do not appear in any of the downloaded .js files. You can also attempt to pass a non‑serializable prop, such as a function, from the Server Component to a Client Component and observe the build‑time error.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.