BRUCE_FEBRUCE_FE

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.

影片縮圖

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 Interactive

Frontend Rendering Method Comparison

Rendering MethodHow It WorksAdvantagesDisadvantagesSuitable Scenarios
SSR(Server-Side Rendering)Server generates HTML and sends it to the browser
  • Better First Contentful Paint time
  • SEO friendly
  • Low-end device friendly
  • Suitable for dynamic content
  • High server load
  • Longer TTFB
  • Full page refresh
  • High development complexity
  • Amazon, Shopify (e-commerce)
  • CNN, BBC (news sites)
  • Airbnb, Booking.com
SSG(Static Site Generation)Pre-renders into static HTML files at build time
  • Super fast page loading speed
  • Excellent SEO
  • Low cost and high security
  • Reduces server load
  • Updates not immediate
  • Not suitable for highly dynamic content
  • Long build time
  • Development complexity
  • Next.js docs, MDN
  • Gatsby blogs, Vercel
  • Stripe, Netlify websites
CSR(Client-Side Rendering)Browser dynamically generates page content using JS
  • Rich interactive experience
  • Fast page transitions
  • Reduces server load
  • Complete separation of frontend and backend
  • Slower initial load
  • SEO challenges
  • Poor performance on low-end devices
  • Possible white screen issues
  • Gmail, Google Maps
  • Facebook, Twitter dashboards
  • Spotify, Discord

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 components

Main 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:

ConsiderationsSSRSSGCSR
Content update frequencyFrequently changing content (social media feeds, news sites)Less frequently updated content (blogs, company websites)Applications requiring real-time interaction (Gmail, Facebook apps)
SEO requirementsHigh (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 experienceQuick content visibility, but possibly brief interaction delaysFastest first screen loading (e.g., e-commerce product pages)Slower initial load, but smooth subsequent page transitions (single-page apps)
API data fetchingSecure server-side fetching (user profile pages)One-time fetching at build time (product catalog pages)Browser-side fetching (weather apps, stock dashboards)
Technical resourcesRequires stronger servers (rendering for each request)Requires resources at build time, but can deploy to simple static hostsLight 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 MethodFirst-time Loading Process
SSR
  1. Browser sends HTTP request
  2. Server executes React code to generate HTML
  3. Browser receives complete HTML and displays it immediately
  4. JavaScript downloads and parses in parallel
  5. Hydration process: JS code takes over existing HTML elements and adds event handling
CSR
  1. Browser sends HTTP request
  2. Server returns basic HTML shell (usually just a root div)
  3. Browser downloads JavaScript bundle
  4. JavaScript parses and executes
  5. React creates virtual DOM and renders actual DOM
  6. Page content finally displays

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 useEffect to handle client-specific logic
  • Be especially careful when using dangerouslySetInnerHTML
  • Use dynamic imports to separate client-specific code