How do performance, SSR, and SEO factor into frontend system design?
Three intertwined concerns. SSR ships fully rendered HTML so first paint is fast (LCP wins) and crawlers see real content (SEO wins). SSG goes further by precomputing at build for static cacheable HTML. CSR alone leaves crawlers with an empty shell and pushes first paint past JS bundle download. Pick per page: SSG for marketing/blog, SSR/ISR for dynamic-but-shared content, CSR for authenticated dashboards. Layer in metadata (title, description, OG, JSON-LD), sitemap, canonical URLs.
Performance, SSR, and SEO overlap because they all depend on what the browser (and crawler) sees and how fast it sees it.
Rendering modes
| Mode | What ships | First paint | SEO (Google + others) | Best for |
|---|---|---|---|---|
| CSR | Empty shell + JS bundle | After JS bundle parses + runs | Spotty for non-Google crawlers | Auth-only dashboards, internal tools |
| SSR | Full HTML, then hydrate | Network-bound TTFB + paint | Reliable | Personalized but shared content |
| SSG | Pre-rendered HTML on CDN | TTFB ≈ CDN latency | Reliable + cacheable | Marketing, docs, blog |
| ISR | SSG + on-demand regen | Like SSG, freshness like SSR | Reliable | Catalogs, frequently-updated content |
The pattern: render closer to the user = faster first paint + better SEO. CDN-cached pre-rendered HTML is the gold standard. Personalization pushes you toward SSR; pure interactivity (logged-in app) toward CSR.
Why SEO needs server rendering
Googlebot does run JS, but with a budget — heavy JS pages can be deferred, partially indexed, or have stale snapshots. Other crawlers (Bing, Baidu, social previews, LinkedIn, link unfurlers in Slack/iMessage) don't run JS. If you care about discoverability or shareable links, server-rendered HTML is non-negotiable.
For social previews specifically:
<meta property="og:title">,<meta property="og:image">,<meta property="og:description">must be in the initial HTML response.- Twitter cards:
<meta name="twitter:card">. - These are read by crawlers that pull the HTML once and don't run JS.
SEO checklist
- Server-rendered (or static) HTML for any indexable page.
- Unique
<title>and<meta name="description">per page. - Open Graph + Twitter Card metadata.
- Canonical URL (
<link rel="canonical">) to prevent duplicate content penalties. - Sitemap.xml + robots.txt with the sitemap URL.
- Structured data (JSON-LD) for rich results:
Article,Product,FAQPage,BreadcrumbList. - Semantic HTML — headings hierarchy, real
<a href>links (crawlers follow). - Internal linking structure (sidebar, related links).
- Fast LCP — speed is a ranking signal.
- Mobile-friendly — Google's primary index is mobile.
Why performance also matters for SEO
Google uses Core Web Vitals as a ranking factor (Page Experience). LCP, INP, CLS in the good range = ranking boost (small but real). Slow LCP increases bounce rate, which is a user signal Google sees indirectly.
How to pick per page
Marketing pages (homepage, about, pricing, blog posts): SSG. Pre-rendered, cached on CDN, sub-second TTFB worldwide. Maximum SEO + speed.
Product detail pages (e-commerce catalog): ISR. Pre-rendered but revalidates so inventory/price updates flow within minutes.
Search results / category pages: ISR with short revalidate, or SSR with edge caching.
Personalized feeds (logged-in homepage): SSR with per-user cookies. SEO not relevant; LCP still matters for repeat visitors.
App / dashboard (logged-in): CSR is fine. SEO doesn't apply; speed comes from cached API + responsive UI.
Concrete Next.js example
// app/products/[slug]/page.tsx — ISR with on-demand revalidation
export const revalidate = 3600; // 1h fallback
export async function generateStaticParams() {
const products = await db.product.findMany({ select: { slug: true } });
return products.map(({ slug }) => ({ slug }));
}
export default async function ProductPage({ params }) {
const product = await db.product.findUnique({ where: { slug: params.slug } });
return (
<>
<h1>{product.name}</h1>
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'Product',
name: product.name,
image: product.image,
offers: { '@type': 'Offer', price: product.price, priceCurrency: 'USD' },
})}} />
…
</>
);
}
export async function generateMetadata({ params }) {
const product = await db.product.findUnique({ where: { slug: params.slug } });
return {
title: `${product.name} | My Store`,
description: product.summary,
openGraph: { images: [product.image] },
alternates: { canonical: `https://example.com/products/${params.slug}` },
};
}Tradeoffs
- SSR is slower TTFB than SSG (runs per request). Mitigate with edge runtime + caching.
- SSG has stale content if data changes between builds. ISR / revalidateTag fixes this.
- CSR has fast TTFB (static shell) but slow LCP (waits for JS). Worst of both for SEO.
- Edge rendering trades some compute flexibility (limited Node APIs) for global low latency.
Lighthouse SEO audit + Real-user
Lighthouse SEO catches the basic checklist (meta, semantic HTML, link contrast). Real SEO success needs Search Console — it tells you what Google is actually indexing, crawl errors, Core Web Vitals from CrUX, mobile usability issues.
Mental model
Speed + structure + discoverability. SSR/SSG win because they put real content in the initial HTML, which crawlers read and users see fast. CSR loses on both. Pick the rendering mode per page based on who needs to see what and when.
Follow-up questions
- •How does Googlebot's JS rendering differ from a real browser?
- •When does ISR's stale-while-revalidate cause SEO issues?
- •What's the role of canonical URLs?
- •How do you handle metadata for client-side route changes in an SPA?
Common mistakes
- •CSR for marketing pages — empty HTML for non-Google crawlers, social previews break.
- •Missing OG tags — links shared on Slack/iMessage/LinkedIn show no preview.
- •Duplicate content from www/non-www and trailing slash without canonical.
- •JSON-LD with wrong schema type or invalid data — silently ignored.
- •Blocking JS render delaying LCP and tanking Page Experience score.
- •robots.txt blocking important paths or sitemap not referenced.
Performance considerations
- •SSR/SSG move TTFB and LCP earlier — typically 1–3s improvement vs CSR for cold visits. Edge rendering (Cloudflare Workers, Vercel Edge) brings TTFB to ~100ms globally. The combination of SSG + edge cache + small client JS is the modern fast-and-cacheable architecture for content sites.
Edge cases
- •Single-page apps need to fire pageview + update metadata on route change.
- •Hash-based routes (#/page) aren't crawled — use history API.
- •Localized pages need hreflang annotations.
- •Lazy-loaded content below the fold may not be crawled if not in initial HTML — important text should always be SSR'd.
- •Pagination with infinite scroll needs rel=next/prev or server-side pagination URLs for SEO.
Real-world examples
- •Next.js App Router + Vercel Edge: Vercel's own marketing site, Notion's marketing pages, many SaaS sites.
- •Shopify storefronts: SSG + Hydrogen for the product layer.
- •News sites (NYT, BBC): server-rendered for SEO + speed.
- •Internal dashboards (Linear, Airtable): mostly CSR — SEO irrelevant, speed comes from cached API.