What is SSR (Server-Side Rendering) and how does it help SEO?
    Skip to main content

    SSR

    SSR returns fully rendered HTML on each request, helping bots and users see content faster and improving indexability and first paint.

    Definition

    SSR (Server-Side Rendering) renders your framework output (e.g., React) into HTML on the server per request. It makes content and metadata/structured data available immediately, which is very friendly to JavaScript SEO.

    Why it matters

    • Ships indexable content immediately without waiting for JavaScript execution
    • More stable first paint: content arrives first, interactions follow after hydration — improves LCP
    • Ideal for real-time or personalized pages like user dashboards
    • Social sharing works correctly: OG tags are in initial HTML for Facebook/Twitter crawlers
    • Supports AI engine indexing: ChatGPT/Perplexity crawlers have limited rendering capabilities
    • Reduces dependence on Googlebot's render queue: page is immediately readable
    • Consistent SEO experience: both users and bots see the same content

    How to implement

    • Ensure SSR output includes complete head: title/description/canonical/hreflang/schema
    • Avoid hydration mismatch: keep initial HTML consistent with CSR output or React will throw errors
    • Use SSG/prerender for static pages to reduce SSR server costs
    • Set caching strategy: use stale-while-revalidate for identical requests to reduce renders
    • Monitor TTFB: SSR adds server processing time — optimize data fetching
    • Handle errors properly: ensure 404/500 pages are also SSR'd with correct status codes
    • Use streaming SSR: React 18+ supports progressive HTML delivery for better first paint

    Examples

    typescript
    // pages/product/[id].tsx
    import { GetServerSideProps } from 'next';
    import Head from 'next/head';
    
    interface ProductProps {
      product: { id: string; name: string; price: number };
    }
    
    export default function ProductPage({ product }: ProductProps) {
      return (
        <>
          <Head>
            <title>{product.name} | My Store</title>
            <meta name="description" content={`Buy ${product.name} for ${product.price}`} />
            <link rel="canonical" href={`https://mystore.com/product/${product.id}`} />
          </Head>
          <h1>{product.name}</h1>
          <p>Price: ${product.price}</p>
        </>
      );
    }
    
    export const getServerSideProps: GetServerSideProps = async ({ params, res }) => {
      const product = await fetchProduct(params?.id as string);
      if (!product) {
        return { notFound: true }; // Returns 404
      }
      // Set caching (CDN-friendly)
      res.setHeader('Cache-Control', 's-maxage=60, stale-while-revalidate=300');
      return { props: { product } };
    };
    typescript
    // app/routes/blog.$slug.tsx
    import { json, LoaderFunctionArgs, MetaFunction } from '@remix-run/node';
    import { useLoaderData } from '@remix-run/react';
    
    export const loader = async ({ params }: LoaderFunctionArgs) => {
      const post = await getPost(params.slug!);
      if (!post) {
        throw new Response('Not Found', { status: 404 });
      }
      return json(post, {
        headers: { 'Cache-Control': 's-maxage=3600' }
      });
    };
    
    export const meta: MetaFunction<typeof loader> = ({ data }) => {
      if (!data) return [{ title: 'Not Found' }];
      return [
        { title: data.title },
        { name: 'description', content: data.excerpt },
        { property: 'og:title', content: data.title },
      ];
    };
    
    export default function BlogPost() {
      const post = useLoaderData<typeof loader>();
      return <article><h1>{post.title}</h1><div>{post.content}</div></article>;
    }

    Related

    Field notes / FAQ

    FAQ

    Common questions about this term.

    Back to glossary