SSR And Prerendering
7 min read- Server-Side Rendering (SSR) and Pre-rendering
- Overview
- Core Concepts
- What Is Server-Side Rendering (SSR)?
- What Is Pre-rendering (Static Site Generation)?
- When to Use SSR/Pre-rendering
- The Reality: Why SSR Is Often Overkill
- 1. Requires Maintenance and Development
- 2. Consumes Server Compute Time
- 3. Doesn't Produce 1:1 Result (Cloaking Concerns)
- 4. Each Request Served from Origin
- 5. Still Requires Hydration
- Detailed Comparison
- SSR vs Client-Side Rendering
- SSR vs Static Site Generation (SSG)
- Alternatives to Consider
- 1. Client-Side Rendering with Proper SEO Tags
- 2. Incremental Static Regeneration (ISR)
- 3. Edge-Side Rendering
- 4. Hybrid Approach
- Google's Actual Crawling Capabilities
- Implementation Complexity Comparison
- Simple SPA (Minutes to Deploy)
- SSR Application (Days to Production-Ready)
- Questions to Ask Before Implementing SSR
- Summary
- SSR/Pre-rendering Downsides
- When SSR Makes Sense
- When to Skip SSR
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
| Aspect | SSR | Client-Side Rendering |
|---|---|---|
| First Paint | Faster (HTML ready) | Slower (wait for JS) |
| Time to Interactive | Slower (hydration needed) | When JS loads |
| Server Cost | Higher (compute per request) | Minimal (static hosting) |
| Complexity | Higher | Lower |
| Caching | Difficult | Easy (everything static) |
| Personalization | Complex | Natural |
| Development Speed | Slower | Faster |
| Debugging | Harder (server + client) | Easier (client only) |
SSR vs Static Site Generation (SSG)
| Aspect | SSR | SSG |
|---|---|---|
| Build Time | Normal | Longer (pre-generates pages) |
| Content Freshness | Real-time | Stale until rebuild |
| Scalability | Limited by servers | Unlimited (CDN) |
| Cost | Per-request | Per-build |
| Dynamic Content | Supported | Limited |
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
- Is SEO actually critical for this application?
- Internal tools: No
- Marketing sites: Yes
- Authenticated apps: Usually no
- Do you have the infrastructure expertise?
- Server management
- Scaling strategies
- Monitoring and debugging
- What's the cost-benefit analysis?
- Development time added
- Infrastructure costs
- Maintenance burden
- vs. SEO benefit
- Have you tried alternatives first?
- Static meta tags
- Sitemap submission
- Structured data
- Core Web Vitals optimization
Summary
SSR/Pre-rendering Downsides
| Issue | Impact |
|---|---|
| Maintenance overhead | Ongoing development cost |
| Server compute costs | Higher infrastructure bills |
| Content discrepancy | SEO/cloaking concerns |
| Origin dependency | Latency, availability risks |
| Hydration requirement | Delayed interactivity |
| Debugging complexity | Harder to troubleshoot |
| Team knowledge gap | Requires 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.