Skip to main content

    Prerendering

    Prerendering outputs static HTML for routes at build time, combining SPA interactivity with indexable HTML for SEO.

    Definition

    Prerendering renders routes during build and writes HTML files per URL. It's common for React Router sites: serve HTML first for SEO, then hydrate on the client for interactivity — without full SSR at runtime.

    Why it matters

    • Indexable HTML per route for more reliable crawling
    • Fast delivery via CDN-served static files with minimal TTFB
    • Cheaper than full SSR for content-heavy sites — no server needed
    • SEO-friendly: ensures search engines see complete HTML with meta tags
    • Compatible with any static host (Cloudflare Pages, Netlify, Vercel)
    • Preserves SPA interactivity — no sacrifice on user experience
    • Easier debugging: view each page's output at build time

    How to implement

    • List routes to prerender (home, tutorials, tools, glossary)
    • Make head tags route-aware (title/canonical/hreflang/schema)
    • Avoid SPA catch-all rewrites that override per-route index.html
    • Use react-snap, vite-plugin-ssr, or write a custom prerender script
    • Output each route to its own index.html (e.g., /glossary/seo/index.html)
    • Embed JSON-LD and Open Graph during the prerender phase
    • Validate after build: curl each route to confirm complete HTML

    Examples

    html
    // scripts/prerender.ts
    import { renderToString } from 'react-dom/server';
    import { StaticRouter } from 'react-router-dom/server';
    import App from '../src/App';
    import fs from 'fs';
    
    const routes = ['/', '/glossary/seo', '/tools/sitemap'];
    
    for (const route of routes) {
      const html = renderToString(
        <StaticRouter location={route}>
          <App />
        </StaticRouter>
      );
      const dir = `dist${route}`;
      fs.mkdirSync(dir, { recursive: true });
      fs.writeFileSync(`${dir}/index.html`, `<!DOCTYPE html>${html}`);
    }
    html
    // package.json
    {
      "scripts": {
        "build": "vite build",
        "postbuild": "react-snap"
      },
      "reactSnap": {
        "source": "dist",
        "inlineCss": true,
        "puppeteerArgs": ["--no-sandbox"]
      }
    }

    Related

    FAQ

    Common questions about this term.

    Back to glossary