SSR And Prerendering

7 min read
Rapid overview

Server-Side Rendering (SSR) and Pre-rendering

Overview

SSR and pre-rendering are techniques used to generate HTML on the server or at build time, primarily aimed at improving SEO and initial page load performance. However, these approaches come with significant trade-offs that must be carefully considered.

Core Concepts

What Is Server-Side Rendering (SSR)?

  • Dynamic HTML Generation: Server generates HTML for each request
  • Full Page Delivered: Complete HTML sent to client before JavaScript loads
  • SEO Friendly: Search engines can crawl content immediately
  • Framework Support: Next.js, Nuxt.js, Angular Universal, etc.

What Is Pre-rendering (Static Site Generation)?

  • Build-Time Generation: HTML generated during build process
  • Static Files: Pre-built HTML served from CDN
  • Faster Response: No server computation per request
  • Limited Dynamism: Content is static until next build

When to Use SSR/Pre-rendering

Potentially Useful For:

  • Content-heavy sites requiring SEO (blogs, news, documentation)
  • Marketing landing pages
  • E-commerce product pages
  • Sites with mostly static content

Often Not Worth It For:

  • Internal dashboards and admin panels
  • Applications behind authentication
  • Highly dynamic real-time applications
  • Small teams without dedicated infrastructure resources
  • Applications where SEO is not critical

The Reality: Why SSR Is Often Overkill

1. Requires Maintenance and Development

SSR adds significant complexity to your application architecture:

// Without SSR - simple client-side rendering
function ProductPage() {
  const [product, setProduct] = useState(null);

  useEffect(() => {
    fetchProduct(id).then(setProduct);
  }, [id]);

  return <ProductDisplay product={product} />;
}

// With SSR - additional server-side logic required
export async function getServerSideProps(context) {
  // Must handle: authentication, cookies, headers
  // Must manage: database connections, caching
  // Must consider: error handling, timeouts
  const product = await fetchProduct(context.params.id);

  return {
    props: { product }
  };
}

function ProductPage({ product }) {
  return <ProductDisplay product={product} />;
}

Additional maintenance burden:

  • Server environment setup and monitoring
  • Node.js version management
  • Memory leak detection
  • Cold start optimizations
  • Error logging and debugging on server

2. Consumes Server Compute Time

Every request requires server processing:

Client Request → Server Processing → HTML Generation → Response

Traditional SPA:
- Server: Serve static files (fast, cheap, cacheable)
- Cost: Minimal

SSR:
- Server: Execute React/Vue, fetch data, render HTML
- Cost: CPU time, memory, database connections per request

Cost implications:

  • Serverless functions charge per execution time
  • More server instances needed for traffic spikes
  • Database connection pooling becomes critical
  • Higher infrastructure costs overall

3. Doesn't Produce 1:1 Result (Cloaking Concerns)

The HTML delivered to search engines may differ from what users actually see:

// Server renders initial state
export async function getServerSideProps() {
  const products = await getProducts(); // Gets 10 products
  return { props: { products } };
}

// Client may show different content
function ProductList({ products }) {
  const [filteredProducts, setFiltered] = useState(products);
  const [userLocation, setUserLocation] = useState(null);

  useEffect(() => {
    // Client-side personalization changes displayed content
    getUserLocation().then(location => {
      setUserLocation(location);
      setFiltered(products.filter(p => p.availableIn(location)));
    });
  }, []);

  // Users see filtered/personalized content
  // Search engines indexed the unfiltered version
  return <Grid products={filteredProducts} />;
}

Cloaking risks:

  • Search engines may see different content than users
  • Personalized content not reflected in indexed version
  • A/B tests show different variants to bots vs users
  • Dynamic pricing shows different prices to crawlers

4. Each Request Served from Origin

Unlike static files that can be served entirely from CDN edge:

Static SPA:
User → CDN Edge (< 50ms) → Cached HTML/JS

SSR:
User → CDN Edge → Origin Server (100-500ms) → Database → Response
                  ↑ Geographic latency
                  ↑ Server processing time
                  ↑ Database query time

Performance implications:

  • Users far from server experience latency
  • Cold starts in serverless add delay
  • Database becomes a bottleneck
  • CDN caching limited (dynamic content)

5. Still Requires Hydration

SSR doesn't eliminate JavaScript - it delays it:

<!-- Server sends this HTML -->
<div id="root">
  <button class="counter">Count: 0</button>
  <p>Click the button to increment</p>
</div>

<!-- But the button doesn't work until... -->
<script src="/app.js"></script>
<!-- ...JavaScript loads and "hydrates" -->
// Hydration process
function App() {
  const [count, setCount] = useState(0);

  // After hydration, button finally becomes interactive
  return (
    <button onClick={() => setCount(c => c + 1)}>
      Count: {count}
    </button>
  );
}

// React attaches event handlers to existing HTML
hydrateRoot(document.getElementById('root'), <App />);

Hydration problems:

  • Time to Interactive (TTI): Users see content but can't interact
  • Hydration mismatch: Server/client differences cause errors
  • Double rendering: Component logic runs twice
  • Bundle size unchanged: Still need full JavaScript bundle
  • "Uncanny Valley": Page looks ready but isn't responsive

Detailed Comparison

SSR vs Client-Side Rendering

AspectSSRClient-Side Rendering
First PaintFaster (HTML ready)Slower (wait for JS)
Time to InteractiveSlower (hydration needed)When JS loads
Server CostHigher (compute per request)Minimal (static hosting)
ComplexityHigherLower
CachingDifficultEasy (everything static)
PersonalizationComplexNatural
Development SpeedSlowerFaster
DebuggingHarder (server + client)Easier (client only)

SSR vs Static Site Generation (SSG)

AspectSSRSSG
Build TimeNormalLonger (pre-generates pages)
Content FreshnessReal-timeStale until rebuild
ScalabilityLimited by serversUnlimited (CDN)
CostPer-requestPer-build
Dynamic ContentSupportedLimited

Alternatives to Consider

1. Client-Side Rendering with Proper SEO Tags

<!-- Even SPAs can have good SEO with proper meta tags -->
<!DOCTYPE html>
<html lang="en">
<head>
  <title>Product Name - Store</title>
  <meta name="description" content="Product description here">
  <meta property="og:title" content="Product Name">
  <meta property="og:image" content="/product-image.jpg">
  <link rel="canonical" href="https://store.com/products/123">
</head>
<body>
  <div id="root"></div>
  <script src="/app.js"></script>
</body>
</html>

2. Incremental Static Regeneration (ISR)

// Next.js ISR - best of both worlds
export async function getStaticProps() {
  const products = await getProducts();

  return {
    props: { products },
    revalidate: 60 // Regenerate at most every 60 seconds
  };
}

// Page is static, but updates periodically

Benefits:

  • Static performance
  • Eventually fresh content
  • No per-request server cost

3. Edge-Side Rendering

// Cloudflare Workers / Vercel Edge
export const config = {
  runtime: 'edge' // Runs at CDN edge, not origin
};

export default async function handler(request) {
  // Lightweight rendering at edge
  // Lower latency than origin SSR
}

4. Hybrid Approach

// Static shell with dynamic islands
function ProductPage({ staticProduct }) {
  return (
    <div>
      {/* Static content - rendered at build time */}
      <ProductInfo product={staticProduct} />

      {/* Dynamic island - client-side only */}
      <ClientOnly>
        <LiveInventory productId={staticProduct.id} />
        <PersonalizedRecommendations />
      </ClientOnly>
    </div>
  );
}

Google's Actual Crawling Capabilities

Modern search engines can crawl JavaScript:

// Google's rendering capabilities (2024+):
// - Full JavaScript execution
// - Waits for dynamic content
// - Understands SPAs
// - Follows client-side routing

// However, there may be delays:
// 1. Initial crawl: HTML only
// 2. Render queue: JavaScript execution (can take days)
// 3. Re-index: Updated content reflected

What actually matters for SEO:

  • Proper meta tags and structured data
  • Fast load times (Core Web Vitals)
  • Mobile responsiveness
  • Good content and link structure
  • Proper sitemap and robots.txt

Implementation Complexity Comparison

Simple SPA (Minutes to Deploy)

npm create vite@latest my-app -- --template react
cd my-app
npm run build
# Deploy dist/ folder to any static host

SSR Application (Days to Production-Ready)

// Server setup
import express from 'express';
import { renderToString } from 'react-dom/server';

const app = express();

app.get('*', async (req, res) => {
  try {
    // Data fetching
    const data = await fetchDataForRoute(req.url);

    // HTML generation
    const html = renderToString(<App data={data} />);

    // Template injection
    const fullHtml = template.replace('<!--app-->', html);

    res.send(fullHtml);
  } catch (error) {
    // Error handling
    res.status(500).send('Server Error');
  }
});

// Plus: process management, logging, monitoring,
// health checks, graceful shutdown, etc.

Questions to Ask Before Implementing SSR

  1. Is SEO actually critical for this application?
  • Internal tools: No
  • Marketing sites: Yes
  • Authenticated apps: Usually no
  1. Do you have the infrastructure expertise?
  • Server management
  • Scaling strategies
  • Monitoring and debugging
  1. What's the cost-benefit analysis?
  • Development time added
  • Infrastructure costs
  • Maintenance burden
  • vs. SEO benefit
  1. Have you tried alternatives first?
  • Static meta tags
  • Sitemap submission
  • Structured data
  • Core Web Vitals optimization

Summary

SSR/Pre-rendering Downsides

IssueImpact
Maintenance overheadOngoing development cost
Server compute costsHigher infrastructure bills
Content discrepancySEO/cloaking concerns
Origin dependencyLatency, availability risks
Hydration requirementDelayed interactivity
Debugging complexityHarder to troubleshoot
Team knowledge gapRequires server expertise

When SSR Makes Sense

  • ✅ Large content sites where SEO drives revenue
  • ✅ Public-facing e-commerce with product pages
  • ✅ News/media sites with time-sensitive content
  • ✅ Teams with dedicated infrastructure resources

When to Skip SSR

  • ❌ Internal dashboards and admin panels
  • ❌ Authenticated-only applications
  • ❌ Small teams without server expertise
  • ❌ Real-time applications
  • ❌ When simpler alternatives work
  • ❌ MVPs and rapid prototypes

Bottom line: SSR is a tool, not a requirement. Modern search engines handle SPAs well, and the complexity of SSR is often not justified by the benefits. Start simple, measure actual SEO impact, and add SSR only when data shows it's necessary.