Server-Side Rendering with Next.js
Published on January 28, 2026
Written by: Code Arc Studio Editorial Team

Next.js is a powerful React framework that offers a flexible approach to data fetching and rendering, extending React's capabilities to build production-grade applications. One of its most notable features is Server-Side Rendering (SSR). With SSR, the HTML for a page is generated on the server for each request, rather than being rendered by the browser using JavaScript (which is known as Client-Side Rendering or CSR). This provides significant benefits for both user experience and search engine optimization (SEO).
In a typical CSR React app, the server sends a minimal HTML file with a JavaScript bundle. The browser must then download, parse, and execute the JavaScript to render the page content. This can lead to a blank screen and a delay before the user sees anything meaningful. With SSR, the server fetches any necessary data, renders the complete HTML page, and sends it to the client. The browser can then immediately display the content. This results in a faster perceived load time and a better First Contentful Paint (FCP). After the initial HTML is loaded, Next.js "hydrates" the page, attaching React event listeners to the existing HTML, turning it into a fully interactive single-page application.
SSR in the Next.js App Router
With the introduction of the App Router in Next.js 13, the way we think about rendering has evolved. By default, all components inside the app directory are React Server Components (RSCs). This means they render exclusively on the server, providing an even more streamlined approach to SSR.
Data fetching in Server Components is incredibly simple—you can use async/await directly within your component. Next.js automatically handles the server-side rendering process. This eliminates the need for special functions like getServerSideProps for many use cases.
// app/posts/[id]/page.tsx - A Server Component
async function getPost(id) {
const res = await fetch(`https://api.example.com/posts/${id}`);
if (!res.ok) return undefined;
return res.json();
}
export default async function PostPage({ params }) {
const post = await getPost(params.id);
if (!post) {
return <div>Post not found.</div>;
}
return (
<article>
<h1>{post.title}</h1>
<p>{post.body}</p>
</article>
);
}
This component will run on the server for every request, fetch the latest data for the given post ID, and send the fully rendered HTML to the client. This is SSR at its simplest.
When to Choose SSR over SSG
Next.js also excels at Static Site Generation (SSG), where pages are pre-rendered to HTML at build time. The choice between SSR and SSG depends on your data.
Rendering Method Comparison
| Method | When it's built | Best for |
|---|---|---|
| SSG (Static Site Generation) | At build time | Blogs, marketing pages, docs. Content that is the same for every user. |
| SSR (Server-Side Rendering) | On each request | Dashboards, social media feeds. Content that is dynamic and personalized. |
| ISR (Incremental Static Regeneration) | At build time, then revalidated | E-commerce, news sites. Content that is mostly static but needs updates. |
Next.js also provides a middle ground with Incremental Static Regeneration (ISR), which allows you to re-generate static pages at a set interval, giving you the performance of static with the freshness of dynamic rendering. You can achieve this by using the revalidate option in your fetch calls.
// This fetch will be cached for 60 seconds.
// After 60s, the next request will get the cached version,
// while Next.js re-fetches and updates the cache in the background.
fetch('...', { next: { revalidate: 60 } });
Understanding these different rendering strategies is key to building high-performance, scalable applications with Next.js.
10 Common Next.js SSR & Data Fetching Errors and Their Fixes
Working with Next.js data fetching and rendering can be complex. Here are common issues developers face.
| # | Common Error | Why It Happens | Solution |
|---|---|---|---|
| 1 | "window is not defined" |
You are trying to access a browser-only API (like window or localStorage) in a Server Component, which renders on the server where these APIs don't exist. |
Move the code that needs browser APIs into a Client Component (add 'use client' at the top of the file) and run it inside a useEffect hook. |
| 2 | Hydration Mismatch Error | The HTML rendered on the server is different from the HTML rendered on the initial client-side render. This often happens with random numbers, dates, or browser-specific data. | Ensure any code that produces different results on server and client (like new Date()) runs inside a useEffect hook in a Client Component. |
| 3 | Data fetched on the server is not fresh on the client. | Next.js aggressively caches fetch requests in Server Components. If you need fresh data on every request, the default cache behavior might be getting in the way. | To force dynamic rendering and fresh data on every request, use fetch('...', { cache: 'no-store' }) or opt the entire page into dynamic rendering with export const dynamic = 'force-dynamic'. |
| 4 | Passing functions or non-serializable data from Server to Client Components. | Props passed from Server to Client Components must be serializable (plain objects, strings, numbers). You cannot pass functions, Dates, Maps, etc. | Convert any complex data to a serializable format before passing it as a prop. For example, convert a Date object to an ISO string: date.toISOString(). |
| 5 | "You're importing a component that needs [hook]... It only works in a Client Component" | You are trying to use a React Hook (like useState or useEffect) or an event handler (like onClick) in a Server Component. |
Add the 'use client' directive at the top of the file to turn it into a Client Component. |
| 6 | Slow server response (high TTFB). | Your data fetching in a Server Component is slow, blocking the page from being rendered and sent to the client. | Optimize your database queries or API calls. Use streaming with <Suspense> to send down a loading shell immediately while the slow data fetches on the server. |
| 7 | Build fails on dynamic routes. | During next build, Next.js tries to pre-render dynamic routes (e.g., /posts/[slug]). If it can't find the necessary data, the build will fail. |
Implement the generateStaticParams function in your dynamic page to tell Next.js which paths to pre-render at build time. |
| 8 | Environment variables are not available on the client. | By default, environment variables are only available on the server. Only variables prefixed with NEXT_PUBLIC_ are exposed to the browser. |
For client-side code, prefix your environment variables with NEXT_PUBLIC_ in your .env file. For server-side code, you can use them directly. |
| 9 | API routes not found in production. | Route Handlers (API routes) in the App Router are deployed as serverless functions. Configuration issues or incorrect file naming can cause them to fail. | Ensure your file is named route.ts (or .js) and that you export functions for HTTP methods like GET, POST, etc. Check your hosting provider's logs. |
| 10 | Search params or headers are static. | Accessing search params or headers in a Server Component can cause the page to be statically rendered at build time with null values, unless you explicitly tell Next.js it's dynamic. | The searchParams prop is automatically passed to page components. Accessing it will opt the page into dynamic rendering. The same applies to using the headers() function from next/headers. |