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
Tutorials
FAQ
Common questions about this term.