EN/CH Mode
BRUCE_FE Interview Notes - Page Loading - Deep Analysis of SSR vs SSG vs CSR
In-depth analysis of the three major frontend rendering methods: SSR, SSG, CSR principles, pros and cons comparison, and a complete breakdown of React Server Components and Hydration mechanisms.
EN/CH Mode

Lazy to read articles? Then watch videos!
Basic Concepts
Modern frontend development has three main page rendering methods: Server-Side Rendering (SSR), Static Site Generation (SSG), and Client-Side Rendering (CSR/SPA). Each has its own advantages and disadvantages, suitable for different application scenarios.
User requests URL
│
↓
Process based on rendering method
│
├─→ SSR: Logic processed on server for each request, real-time rendering
│ └─→ Server generates HTML → Returns complete page
│
├─→ SSG: Logic and data fetching processed during build time (npm run build), HTML pre-generated
│ └─→ Returns static HTML → CDN caching
│
└─→ CSR: Browser rendering
└─→ Returns empty HTML (only <div id="root"></div> in index.html) → Executes JS → Renders page (mounts to div id="root")Simple Code Examples
SSR Example (Next.js 15)
// app/posts/[id]/page.tsx
// Dynamic route page, rendered on the server for each request
export default async function Post({ params }) {
// Server-side data fetching, executed on each request
const res = await fetch(`https://api.example.com/posts/${params.id}`, {
cache: 'no-store' // No caching, ensures latest data
});
const post = await res.json();
return (
<div>
<h1>{post.title}</h1>
<p>{post.content}</p>
<p>Published: {new Date(post.createdAt).toLocaleString()}</p>
</div>
);
}SSG Example (Next.js 15)
// app/blog/[slug]/page.tsx
// Static page generation, pre-rendered at build time (when running npm run build or similar command)
export async function generateStaticParams() {
// Executed at build time, determines which pages to pre-render
const posts = await fetch('https://api.example.com/posts').then(res =>
res.json()
);
return posts.map((post) => ({
slug: post.slug,
}));
}
export default async function BlogPost({ params }) {
// Executed at build time, fetches data for each slug
const post = await fetch(`https://api.example.com/posts/${params.slug}`).then(res =>
res.json()
);
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
<p>Last updated: {new Date(post.updatedAt).toLocaleDateString()}</p>
</article>
);
}CSR Example (React)
// Empty container in HTML file
// index.html
<html>
<head>
<title>CSR Application</title>
</head>
<body>
<div id="root"></div>
<script src="app.js"></script>
</body>
</html>
// Client-side rendering component (app.js)
import React, { useState, useEffect } from 'react';
import ReactDOM from 'react-dom/client';
function Page() {
const [data, setData] = useState(null);
// Fetch data in the browser
useEffect(() => {
async function fetchData() {
const response = await fetch('https://api.example.com/data');
const result = await response.json();
setData(result);
}
fetchData();
}, []);
// Wait for data to load
if (!data) return <div>Loading...</div>;
// Render after data is loaded
return <div>{data.title}</div>;
}
// Inject React application into the empty HTML container
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Page />);Rendering Process Comparison
┌─────────────────────────────────────────────────────────────────────┐
│ SSR Process │
└─────────────────────────────────────────────────────────────────────┘
User Request → Server Renders HTML → Return Complete HTML → Browser Display → JS Load → Interactive
┌─────────────────────────────────────────────────────────────────────┐
│ SSG Process │
└─────────────────────────────────────────────────────────────────────┘
Build Time: Pre-render All Pages → Generate Static HTML Files (during npm run build)
User Request → CDN Returns Static HTML → Browser Display → JS Load → Interactive
┌─────────────────────────────────────────────────────────────────────┐
│ CSR Process │
└─────────────────────────────────────────────────────────────────────┘
User Request → Return Minimal HTML → Load JS → JS Renders → Page Visible → Interactive┌─────────────────────────────────────────────────────────────────────┐
│ Timeline Comparison │
└─────────────────────────────────────────────────────────────────────┘
Time ─────────────────────────────────────────────────────────────────▶
SSR: [Server Rendering]───[First Content Display]───[JS Loading]───[Fully Interactive]
└─Longer TTFB─┘ └─Faster FCP─┘ └─TTI─┘
SSG: [CDN Response]───────[First Content Display]───[JS Loading]───[Fully Interactive]
└─Very Short TTFB─┘ └─Very Fast FCP─┘ └─TTI─┘
CSR: [Load Minimal HTML]──[Load JS]───[Render DOM]───[First Content Display+Interactive]
└─Short TTFB─┘ └─Large JS─┘└─White Screen─┘ └─Slower FCP but simultaneous TTI─┘
TTFB: Time To First Byte FCP: First Contentful Paint TTI: Time To InteractiveFrontend Rendering Method Comparison
| Rendering Method | How It Works | Advantages | Disadvantages | Suitable Scenarios |
|---|---|---|---|---|
| SSR(Server-Side Rendering) | Server generates HTML and sends it to the browser |
|
|
|
| SSG(Static Site Generation) | Pre-renders into static HTML files at build time |
|
|
|
| CSR(Client-Side Rendering) | Browser dynamically generates page content using JS |
|
|
|
React Server Components and Hydration
React Server Components (RSC)
React Server Components are a new feature introduced by the React team that allows us to choose which components will only run on the server without being sent to the browser. This solves a major problem with traditional SSR: previously, all components had to run on both the server and browser sides.
How React Server Components Work
│
├─→ Server Components
│ ├─→ Don't send JavaScript to the browser, only static HTML
│ ├─→ Can directly connect to databases, read files
│ └─→ Help reduce the amount of code downloaded by the browser
│
└─→ Browser Side
├─→ Receives rendering results from server components (pure HTML)
└─→ Seamlessly integrates with browser componentsMain Benefits:
- •Reduced JavaScript download: Server component code isn't sent to browsers, which only receive rendered HTML, significantly reducing network transmission load
- •Direct backend resource access: Server components can directly connect to databases, read file systems, or use server-side APIs without going through frontend API requests
- •Automatic code splitting: The system automatically decides which components run on the server, simplifying development
- •Batch UI transmission: Using React Suspense and Streaming SSR technology, important parts of the page (like navigation bars and main content) can be transmitted first, followed by secondary parts (like comment sections and related products), for example:
React Server Components with Suspense
ExpensiveDataComponent can be a server component that fetches data and renders on the server
Browser Server
┌────────────┐ ┌────────────────────────┐
│ Show Loading│◄── First Send ─┤ <Suspense fallback={...}> │
└─────┬──────┘ │ │
│ │ RSC fetches data on server │
│ │ <ExpensiveDataComponent/> │
┌─────▼──────┐ │ │
│ Show Full │◄── Later Send ─┤ Send rendered HTML and data │
│ Content │ └────────────────────────┘
└────────────┘ Real-world products using RSC:
- •Next.js 13+: Fully integrates RSC, uses Server Components by default in App Router mode
- •Shopify stores: E-commerce sites built with the Hydrogen framework (based on RSC), providing faster product page loading
- •Vercel platform: Its official website and dashboard use RSC to optimize performance
Practical application scenarios:
// Server Component Example - Runs only on the server, no JS sent to browser
// ProductPage.js
async function ProductPage({ productId }) {
// Direct database access, this code won't be sent to the browser
const product = await db.products.findUnique({
where: { id: productId }
});
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<img src={product.imageUrl} alt={product.name} />
{/* Client Component, JS will be sent to browser */}
{/* Note: When actually using it, the AddToCartButton component file needs "use client" directive at the top */}
<AddToCartButton productId={product.id} />
{/* In Next.js, client components must have "use client" directive at the top of the file */}
</div>
);
}Hydration Process
Hydration is the process of converting server-rendered static HTML into an interactive React application.
// Server-side rendering HTML
import { renderToString } from 'react-dom/server';
function App() {
return (
<div>
<h1>Hello World</h1>
<button onClick={() => alert('Clicked')}>Click Me</button>
</div>
);
}
// Generate static HTML string
const html = renderToString(<App />);// Client-side Hydration process
import { hydrateRoot } from 'react-dom/client';
// Inject interactivity, match existing DOM structure and add event listeners
hydrateRoot(
document.getElementById('root'),
<App />
);Handling client-specific features:
function UserProfile() {
// Used to track if hydration has occurred on the client
const [isClient, setIsClient] = useState(false);
// Only executes on the client
useEffect(() => {
setIsClient(true);
}, []);
// Shown on server and during initial hydration
if (!isClient) {
return <div>Loading profile...</div>;
}
// Only shown after client hydration
return (
<div>
<h2>User Profile</h2>
<button onClick={() => alert('Profile updated')}>
Update Profile
</button>
</div>
);
}🔥 Common Interview Questions
(1) How to choose the appropriate rendering method?
When choosing a rendering method, consider the following factors:
| Considerations | SSR | SSG | CSR |
|---|---|---|---|
| Content update frequency | Frequently changing content (social media feeds, news sites) | Less frequently updated content (blogs, company websites) | Applications requiring real-time interaction (Gmail, Facebook apps) |
| SEO requirements | High (search engines can directly crawl complete HTML) | Best (pre-generated pages are most friendly to search engines) | Poor (search engines may not wait for JS rendering to complete) |
| User experience | Quick content visibility, but possibly brief interaction delays | Fastest first screen loading (e.g., e-commerce product pages) | Slower initial load, but smooth subsequent page transitions (single-page apps) |
| API data fetching | Secure server-side fetching (user profile pages) | One-time fetching at build time (product catalog pages) | Browser-side fetching (weather apps, stock dashboards) |
| Technical resources | Requires stronger servers (rendering for each request) | Requires resources at build time, but can deploy to simple static hosts | Light server load, but user devices must handle rendering |
Best practice is to mix different rendering methods based on project requirements. For example, Next.js allows selecting different rendering strategies for different pages within the same application.
(2) What's the difference between SSR and CSR in first-time loading?
| Rendering Method | First-time Loading Process |
|---|---|
| SSR |
|
| CSR |
|
Key Difference: SSR displays initial content faster, but interactivity requires waiting for JS to load; CSR has slower initial display but smoother page transitions after loading. SSR performs better in First Contentful Paint (FCP), while CSR may show loading states or white screens before fully loading.
(3) What is Hydration Mismatch? How to resolve it?
Hydration Mismatch refers to inconsistencies between server-rendered HTML and client-side rendering results.
Common causes:
- •Code that depends on client-side environment (Browser APIs, window object)
- •Time-dependent calculations (new Date())
- •Random numbers (Math.random())
- •Language and timezone differences
Example problems and solutions:
// Bad practice - may cause Hydration Mismatch
function DateDisplay() {
// Server and client may display different times
return <div>{new Date().toLocaleString()}</div>;
}
// Good practice - correctly handling time display
function DateDisplay() {
const [date, setDate] = useState(() => new Date());
useEffect(() => {
// Update time after client-side hydration
setDate(new Date());
}, []);
return <div>{date.toLocaleString()}</div>;
}Strategies to prevent Hydration Mismatch:
- •Use
useEffectto handle client-specific logic - •Be especially careful when using
dangerouslySetInnerHTML - •Use dynamic imports to separate client-specific code