Skip to main content

    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

    FAQ

    Common questions about this term.

    Back to glossary