Search engines index the HTML delivered by the server. A page that renders all content via client-side JavaScript sends search bots an empty <div id='root'> — the product description, pricing, and metadata are invisible until JavaScript executes, which Googlebot may not fully process or may process with a crawl budget delay. This directly harms organic search ranking for the pages most important to business discovery. Beyond SEO, client-side-only rendering means the user sees a blank page until JavaScript loads, parses, and executes — adding 1–4 seconds of blank white screen on slow connections. For SEO-critical pages, this is a dual failure: discoverability and user experience.
Critical because CSR-only SEO pages are effectively invisible to search engines, causing permanent ranking loss for the pages most critical to business discovery.
Convert SEO-critical pages from client-side useEffect data fetching to server-rendered async components. In Next.js App Router, server components are the default — move data fetching out of useEffect and into the component body with async/await.
// Before — CSR only, invisible to search engines
'use client'
export default function ProductPage({ params }) {
const [product, setProduct] = useState(null)
useEffect(() => {
fetch(`/api/products/${params.id}`).then(r => r.json()).then(setProduct)
}, [params.id])
return product ? <ProductDetails product={product} /> : <Spinner />
}
// After — server-rendered, indexed by search engines
export default async function ProductPage({ params }) {
const product = await fetch(`https://api.example.com/products/${params.id}`, {
cache: 'force-cache'
}).then(r => r.json())
return <ProductDetails product={product} />
}
// Metadata also server-rendered
export async function generateMetadata({ params }) {
const product = await fetchProduct(params.id)
return {
title: product.name,
description: product.description,
openGraph: { images: [product.imageUrl] }
}
}
ID: performance-load.rendering.no-client-only-seo
Severity: critical
What to look for: Count all SEO-critical pages (homepage, product pages, landing pages, about, pricing, blog posts). For each, determine the rendering strategy and check whether HTML content and meta tags are present in the server-rendered output (not injected by client-side JavaScript). Look for useEffect data fetching for critical content on important pages. Check for pages that do not exist in build output (all CSR). Enumerate: "X SEO-critical pages found, Y are server-rendered." For a deeper analysis of SEO patterns, the SEO Fundamentals Audit (seo-fundamentals) covers meta tags, structured data, and crawlability in detail.
Pass criteria: 100% of SEO-critical pages are server-rendered (SSR/SSG). Metadata (title, description, og:image) is available in the HTML before hydration. No more than 0 SEO-critical pages should rely on client-side JavaScript for meta tags. Report: "X of Y SEO-critical pages serve HTML content and meta tags from the server."
Fail criteria: 1 or more SEO-critical pages are CSR-only, with all content loaded via JavaScript. Meta tags (title, description, og:image) are set by client-side JavaScript on any SEO-critical page.
Skip (N/A) when: Site has no SEO requirements (0 public-facing pages — e.g., internal tool, authenticated app with no public landing page).
Detail on fail: "2 of 5 SEO-critical pages are CSR-only — homepage and /pricing render blank HTML until JavaScript loads" or "3 of 3 product pages use useEffect for all content — search engines see empty <div id='root'>"
Remediation: Use SSR or SSG for SEO pages:
// Before — CSR only (bad for SEO)
export default function Product() {
const [product, setProduct] = useState(null)
useEffect(() => {
fetch('/api/product').then(data => setProduct(data))
}, [])
return <>{product && <ProductDetails {...product} />}</>
}
// After — SSR (good for SEO)
export default async function Product({ params }) {
const product = await fetch(`https://api.example.com/products/${params.id}`, {
cache: 'no-store'
})
return <ProductDetails product={product} />
}
// Metadata in Next.js
export async function generateMetadata({ params }) {
const product = await fetch(`https://api.example.com/products/${params.id}`)
return {
title: product.name,
description: product.description,
openGraph: { image: product.image }
}
}
Cross-reference: For SEO impact of client-side rendering, the SEO Fundamentals audit covers indexability and meta tag requirements in detail.