Exploring Next.js: A Beginner's Guide to SSR, CSR


What is Next.js?
Next.js is a modern full stack web framework built on top of React that helps us to create fast, scalable, and SEO-friendly web applications. Unlike React, which is mainly a library for building user interfaces, Next.js provides a complete toolkit, including routing, server-side rendering (SSR), static site generation (SSG), and API routes.
Key features of Next.js
File Based Routing
Every folder we create in the app folder automatically becomes a route. So, there is no need for extra libraries or complex route setup. We just create a folder, and it’s a route.
Server-Side Rendering (SSR)
Pages are rendered on the server, and the HTML is sent to the browser. It improves SEO and initial load speed because search engines and users see the content immediately.
API Routes
We can create backend API endpoints directly in your Next.js app. So, there is no need for a separate backend server for simple APIs.
Client and Server Components
Client Components
They are the components that are rendered on the server by default. They don't include client-side JavaScript. They are used for static content like blog posts or marketing pages, fetching data from a database or API during rendering, rendering non-interactive UI elements.
Features
Server-Side Rendering: They are executed on the server during the initial page load or during navigation.
No Client-Side Overhead: They reduces the amount of JavaScript sent to the browser, improving performance.
Direct Access to Server Resources: They can access databases, file systems, or APIs directly without needing client-side fetching.
Default in Next.js: All components in the app directory are Server Components unless marked as Client Components.
Limitations
Cannot use browser APIs (e.g., window, localStorage).
Cannot use React hooks like useState or useEffect.
Cannot handle client-side interactivity (e.g., onClick handlers).
Server Components
They are the components that are rendered on the client. They include client-side JavaScript to enable interactivity. They are used for interactive UI elements like forms, modals, or buttons. They are used in components relying on browser APIs or client-side state.
Features
Client-Side Rendering: They are fully or partially executed in the browser.
Interactivity: They support event handlers, browser APIs, and React hooks (useState, useEffect, etc.).
Hydration: Server-rendered HTML is hydrated on the client to attach event listeners and state.
Explicit Declaration: We must include the "use client" directive at the top of the file.
Limitations
Increases JavaScript bundle size sent to the client.
Slower initial load compared to Server Components due to hydration.
Cannot directly access server-side resources (e.g., databases) without an API layer.
SSR vs CSR
SSR (Server-Side Rendering)
Server-Side Rendering (SSR) is a technique where the server generates the complete HTML for a web page on each user request and sends it to the browser. In Next.js, SSR is implemented using Server Components in the App Router.
How SSR Works?
When a user visits a page, the server processes the request, fetches any necessary data (e.g., from a database or API), and builds the HTML. This fully rendered HTML is sent to the browser, which displays it immediately. If the page needs interactivity, like buttons or forms, Next.js sends JavaScript to “hydrate” the page, enabling client-side features.
Why SSR?
SSR is excellent for SEO because search engines can easily crawl the pre-rendered HTML, making it ideal for blogs, e-commerce, or news sites. It provides a fast initial page load, as users see content without waiting for JavaScript to run. SSR also allows direct access to server-side resources like databases, reducing the need for client-side API calls. In Next.js, Server Components (default in the App Router) minimize JavaScript sent to the browser, boosting performance.
Limitations with SSR
SSR increases server load because the server renders the page for every request, which can slow down response times for high-traffic sites. It’s less suited for highly interactive apps, as interactivity requires additional JavaScript for hydration. SSR also cannot use browser-specific features (e.g., window or localStorage) since it runs on the server. In Next.js, Server Components can’t use React hooks like useState or useEffect.
Example
interface Post {
id: number;
title: string;
body: string;
}
interface Params {
id: string;
}
async function fetchPost(id: string): Promise<Post> {
const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`);
return res.json();
}
export default async function BlogPost({ params }: { params: Params }) {
const post: Post = await fetchPost(params.id);
return (
<div>
<h1>{post.title}</h1>
<p>{post.body}</p>
</div>
);
}
CSR (Client-Side Rendering)
Client-Side Rendering (CSR) is a technique where the server sends a minimal HTML file with JavaScript, and the browser uses that JavaScript to fetch data and render the page. In Next.js, CSR is achieved using Client Components marked with the "use client" directive in the App Router.
How CSR Works?
The server sends a basic HTML shell and a JavaScript bundle to the browser. The browser executes the JavaScript, which fetches data (e.g., via an API) and renders the UI. In Next.js, a Client Component with "use client" uses React hooks like useState or useEffect to manage state and data fetching.
Why CSR?
CSR enables interactive experiences, like forms, dashboards, or real-time apps, because it supports event handlers (e.g., onClick) and browser APIs (e.g., localStorage). It reduces server load since rendering happens on the client. In Next.js, Client Components allow full use of React hooks, enabling dynamic updates without reloading the page.
Limitations with CSR
CSR can lead to slower initial page loads, as the browser must download and execute JavaScript before showing content, which can be an issue on slow devices or networks. It’s less SEO-friendly, as search engines may struggle to index JavaScript-rendered content. CSR also increases the JavaScript bundle size, which can impact performance.
Example
"use client";
import { useState, useEffect } from "react";
interface Todo {
id: number;
title: string;
completed: boolean;
}
export default function CounterPage() {
const [count, setCount] = useState<number>(0);
const [data, setData] = useState<Todo | null>(null);
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/todos/1")
.then((res) => res.json())
.then((result: Todo) => setData(result));
}, []);
return (
<div>
<h1>Client-Side Counter</h1>
{data && <p>Todo: {data.title}</p>}
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
Subscribe to my newsletter
Read articles from Ashish Shrestha directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
