JavaScript SEO
JavaScript SEO focuses on how bots crawl and render JS. The most reliable fix is per-route HTML via SSR/SSG/prerender plus correct metadata/schema.
Definition
JavaScript SEO covers indexability strategies for JavaScript-driven sites. Pure CSR/SPAs require bots to download and execute JS before the DOM appears, which is slower and less reliable. The most stable approach is SSR or build-time prerendering (SSG) so each route ships meaningful HTML first.
Why it matters
- Lower crawl/render cost — Googlebot's render queue is limited, so don't rely on it
- Avoid 'visible but not searchable' delays common in pure CSR apps
- Ensure metadata and structured data are available immediately for SEO/AEO
- Social sharing depends on initial HTML — Facebook/Twitter crawlers don't execute JS
- Improve Core Web Vitals — LCP needs meaningful HTML, not waiting for JS bundles
- Support AI engine indexing — ChatGPT/Perplexity crawlers have limited rendering
- Reduce soft 404 risks — dynamic routes may appear empty to bots if not handled
How to implement
- Deliver per-route HTML via SSR or SSG/prerender — avoid pure CSR
- Ensure head tags are correct: title/description/canonical/hreflang/schema in initial HTML
- Validate rendering: View Source, Search Console URL Inspection, Rich Results Test
- Return proper HTTP status codes — 404 pages should return 404, not 200 with empty content
- Handle lazy loading — ensure main content is visible in HTML without scrolling
- Avoid hydration mismatch — SSR/SSG output must match CSR result
- Monitor Crawl Stats — Search Console crawl statistics reveal rendering issues
Examples
typescript
// vite.config.ts - using vite-plugin-ssr or vite-ssg
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
// Combine with prerender script
// node scripts/prerender.js
});
// scripts/prerender.js
import { renderToString } from 'react-dom/server';
import { StaticRouter } from 'react-router-dom/server';
import App from '../src/App';
const routes = ['/glossary/javascript-seo', '/glossary/ssr'];
for (const route of routes) {
const html = renderToString(
<StaticRouter location={route}>
<App />
</StaticRouter>
);
// Write to dist/[route]/index.html
}typescript
// src/components/SEOHead.tsx
import { Helmet } from 'react-helmet-async';
interface SEOHeadProps {
title: string;
description: string;
canonical: string;
schema?: object;
}
export function SEOHead({ title, description, canonical, schema }: SEOHeadProps) {
return (
<Helmet>
<title>{title}</title>
<meta name="description" content={description} />
<link rel="canonical" href={canonical} />
{schema && (
<script type="application/ld+json">
{JSON.stringify(schema)}
</script>
)}
</Helmet>
);
}
// Usage: ensure Helmet is properly rendered during SSR
// After renderToString, call helmet.renderStatic()Related
Tutorials
Tools
FAQ
Common questions about this term.