What is Server-side Rendering (SSR)

What is Server-side Rendering (SSR)

SSR means the server generates the full HTML for each request, so users see a rendered page immediately, and JavaScript then hydrates it for interactivity.

Long before the Single Page Application (SPA) era, nearly all web pages were server-side rendered: the server would accept a request, run code (PHP, Ruby, Java, Python, etc.), fetch data, generate HTML, and send it out. In essence, SSR is one of the original models of the web.

As browsers became more powerful and JavaScript frameworks matured, client-side rendering (CSR)—or SPA-style rendering—gained favor: the server sends a minimal HTML shell and JavaScript takes over all rendering. But over time, developers noticed drawbacks: slow first loads, SEO challenges, and degraded performance on low-end devices or poor networks. That led to SSR’s resurgence in the modern era.

Today, SSR is not just “old school” templating; it’s often blended with client-side hydration and partial hydration strategies. Frameworks like Next.js, Nuxt, Remix, SvelteKit, and others provide SSR capabilities (sometimes in hybrid modes).

What is SSR and how it works

Server-side rendering (SSR) means that when a browser requests a page, the server executes logic, queries data, and returns fully rendered HTML—with CSS and initial content—rather than handing off rendering entirely to the client. After the HTML arrives, client-side JavaScript can “hydrate” it—attach event handlers, make parts interactive, etc.

Here’s a simplified flow:

flowchart LR
  A[Browser request for /page] --> B[Server receives request]
  B --> C[Server runs application logic, fetches data, renders HTML]
  C --> D[Server sends full HTML + CSS + JS]
  E[Client receives content] --> F[Client renders HTML instantly]
  F --> G[Client hydrates parts with JS / attaches interactivity]

Because the server sends already-rendered HTML, the user sees meaningful content immediately (before JavaScript execution). Then the client applies interactivity, making the page behave like an SPA (if designed). That hydration step is central to SSR-based modern web apps.

When using SSR in JavaScript frameworks, often the same code (or components) is used both on server and client (so-called isomorphic or universal rendering). For instance, the server may run renderToString (or renderToPipeableStream) to produce HTML out of the React tree, then the same React code hydrates on the browser side.

Why SSR still matters today

Improved first-load performance

Because the browser receives ready-made HTML, the user sees content far earlier—even in environments where JavaScript is slow to download or parse.

SEO and web crawlers

Search engine bots and social media scrapers often prefer or expect HTML content already present. SSR ensures crawlers see your actual content, not just an empty shell waiting for JS.

Device and network resilience

On weaker devices or slow networks, relying entirely on CSR can cause blank screens or delays while downloading and executing JS. SSR helps mitigate that by shifting rendering work to the server.

Code reuse and consistency

SSR enables you to share components, data-fetching logic, and rendering logic between server and client, reducing duplication and keeping a unified mental model.

Trade-offs, pitfalls, and complexity

SSR is powerful, but it isn’t free. You’ll need to weigh its costs and handle various challenges.

Server load and latency

Every request triggers rendering and data fetching. If you expect high traffic or heavy pages, your server capacity and response latency become critical considerations.

Caching and invalidation

Because content is dynamic, caching becomes more complex than static content. You’ll need to design cache strategies, stale-while-revalidate rules, partial caching, or edge caching.

Hydration overhead

While SSR gives you initial HTML, you still need client-side JavaScript to make the page interactive. That hydration step can introduce performance bottlenecks (especially if you hydrate huge component trees at once).

Recent work (e.g. modular hydration, adaptive hydration) aims to optimize by delaying or splitting hydration among components, based on device/network conditions.

Complexity of mixed rendering

Often you’ll combine SSR with client-only components, partial hydration, or even static generation for certain routes. Managing routing, data fetching, error handling, and render boundaries can get messy.

Latency of server → user

Although the user sees HTML quickly, server round-trip time (network) still matters. Also, heavy server-side computation (e.g. deep queries, many joins) can degrade responsiveness.

What you need to know as a web developer

When to choose SSR (or hybrid)

  • You need personalized or user-specific content on page load
  • You want tight SEO control or integration with share/embed previews
  • You have pages that must always be fresh (news feeds, dashboards)
  • Your application complexity is suited for shared logic between server and client

But, when most content is stable and doesn’t change per-request, pure SSR may be overkill—you might combine SSR with static generation, or use incremental revalidation approaches (as you saw earlier in SSG discussion).

Design considerations and best practices

  • Split data fetching and rendering: don’t fetch everything blindly. Use boundaries or conditional loading so non-critical parts don’t block rendering.
  • Streaming and incremental rendering: modern frameworks let you send HTML progressively (stream) rather than waiting for full render.
  • Hydrate selectively: only hydrate components that need interactivity (use techniques like “islands” or partial hydration).
  • Edge / CDN + server caching: leverage edge caching or server-level caches to reduce rendering load for repeated requests with same input.
  • Fallbacks and SSR fallbacks: design what happens when data is missing, null, or errors occur.
  • Graceful degradation: if JS fails to run, the SSR HTML should still work or degrade cleanly.

Example code snippet (in a React + Node/Express style)

// server.js (Node / Express + React SSR)

import express from 'express';
import ReactDOMServer from 'react-dom/server';
import App from './App';
import fetchData from './fetchData';

const app = express();

app.get('/post/:id', async (req, res) => {
  const id = req.params.id;
  const post = await fetchData(id);
  const html = ReactDOMServer.renderToString(
    <App initialData={post} />
  );
  res.send(`
    <!DOCTYPE html>
    <html>
      <head><title>${post.title}</title></head>
      <body>
        <div id="root">${html}</div>
        <script>
          window.__INITIAL_DATA__ = ${JSON.stringify(post)};
        </script>
        <script src="/client.bundle.js"></script>
      </body>
    </html>
  `);
});

app.listen(3000);

On the client:

// client entry point

import React from 'react';
import { hydrateRoot } from 'react-dom/client';
import App from './App';

const data = window.__INITIAL_DATA__;
hydrateRoot(document.getElementById('root'), <App initialData={data} />);

Here, the server produces HTML with content, and the client hydrates with the same React tree using initial data.

Relation to SSG and hybrid models (tie-in)

You may remember from discussions of SSG that SSR fits into a continuum of rendering strategies. SSR handles dynamic, per-request rendering, while SSG pre-renders at build time. Hybrid models (e.g. incremental regeneration, on-demand rendering) let you balance between them. Use SSR for pages that must respond to dynamic data or personalization; rely on SSG for stable content pages; and mix in clever caching or revalidation between them.

Summary and guiding principles

SSR is about shifting rendering from the client back to the server—regaining control over first load, SEO, and data freshness. But that power comes with responsibilities: managing server capacity, hydration overhead, caching, complexity, and graceful failure modes.

As you craft web applications, don’t treat SSR as a one-size-fits-all. Use it where it matters, optimize hydration, consider hybrid strategies, and always profile from real devices and networks. If you like, I can build you SSR reference pages using Next.js, SvelteKit, or other frameworks, with real code and performance checks.

Was this helpful?

Thanks for your feedback!

Leave a comment

Your email address will not be published. Required fields are marked *