How to Implement Server-Side Rendering (SSR) in Gatsby: Complete Guide with Trade-offs
Export an async function getServerData(context) from any Gatsby page to enable Server-Side Rendering, which fetches fresh data on every request and merges it with static GraphQL query results before streaming HTML to the client.
Server-Side Rendering (SSR) in Gatsby allows you to generate HTML dynamically at request time rather than build time. While Gatsby defaults to Static Site Generation (SSG) for maximum performance, the gatsbyjs/gatsby repository provides a robust SSR pipeline for pages that require user-specific or rapidly changing data.
How SSR Works in Gatsby
When a page exports getServerData, Gatsby treats it as an SSR-enabled route. The rendering pipeline, orchestrated in packages/gatsby/src/utils/page-ssr-module/entry.ts, executes the following sequence on every request:
- Resolves the page component and runs any build-time GraphQL page query (the SSG portion).
- Invokes the exported
getServerDatafunction frompackages/gatsby/src/utils/get-server-data.tsto fetch request-specific data. - Merges the static query result (
data) with the runtime result (serverData) into the page props. - Streams the fully rendered HTML back to the client.
This architecture allows you to combine the benefits of static builds for global data with dynamic fetching for personalized content.
Implementing getServerData
Basic SSR Page Structure
To enable SSR, export an async function named getServerData from your page component. This function receives a context object containing request parameters and must return an object with a props key.
// src/pages/using-ssr.js
import * as React from "react"
import { Link } from "gatsby"
import Layout from "../components/layout"
const UsingSSR = ({ serverData }) => (
<Layout>
<h1>Server-Side Rendered Page</h1>
<p>Random dog image fetched on every request:</p>
<img
style={{ width: "320px", borderRadius: "4px" }}
alt="A random dog"
src={serverData.message}
/>
<Link to="/">← Back to home</Link>
</Layout>
)
export default UsingSSR
export async function getServerData() {
try {
const res = await fetch(
`https://dog.ceo/api/breed/shiba/images/random`
)
if (!res.ok) throw new Error(`Failed to fetch`)
return { props: await res.json() }
} catch (error) {
return {
status: 500,
headers: {},
props: {}
}
}
}
Source: starters/default/src/pages/using-ssr.js
Handling Headers and Status Codes
The getServerData function can control the HTTP response by returning status and headers properties alongside props. This is useful for setting cache directives or returning error codes.
export async function getServerData({ headers, query }) {
const cacheControl = `public, max-age=60, stale-while-revalidate=30`
const userName = query?.name || "guest"
return {
status: 200,
headers: {
"Cache-Control": cacheControl
},
props: {
greeting: `Hello, ${userName}!`,
timestamp: new Date().toISOString()
}
}
}
Headers defined here are merged with any global SSR headers configured in gatsby-config.js.
Combining Static Queries with SSR Data
SSR pages can simultaneously use Gatsby's static GraphQL queries for build-time data and getServerData for request-time data. Both results are merged into the component's props.
import { graphql } from "gatsby"
export const pageQuery = graphql`
query SiteInfo {
site {
siteMetadata {
title
description
}
}
}
`
const HybridPage = ({ data, serverData }) => (
<div>
<h1>{data.site.siteMetadata.title}</h1>
<p>Static description: {data.site.siteMetadata.description}</p>
<p>Dynamic user data: {serverData.userPreference}</p>
</div>
)
export async function getServerData({ headers }) {
const userId = headers["x-user-id"]
return {
props: {
userPreference: await fetchUserSettings(userId)
}
}
}
This hybrid approach minimizes server workload by pre-building global content while preserving dynamic capabilities for user-specific sections.
SSR vs. SSG: Critical Trade-offs
Understanding when to use Server-Side Rendering versus Static Site Generation requires evaluating several technical dimensions:
| Aspect | SSR (Server-Side Rendering) | SSG (Static Site Generation) |
|---|---|---|
| Time to First Byte | Higher latency due to server execution of getServerData and React rendering per request. |
Instant delivery from CDN edge nodes with no server computation. |
| Infrastructure | Requires Node.js runtime (gatsby serve, Gatsby Cloud, or serverless functions). Cannot deploy to pure static hosts. |
Deployable to any static CDN (Netlify, Vercel, Cloudflare Pages, S3). |
| Build Scalability | Faster builds for large sites; only static portions are pre-built. | Build time scales linearly with page count; impractical for millions of pages. |
| Data Freshness | Real-time data on every request via getServerData. |
Data frozen at build time; requires rebuilds to update content. |
| Caching Strategy | Manual cache control via response headers; default is no cache. | Automatic immutable CDN caching; content hashed by content. |
| Complexity | Must handle errors, status codes, and async data fetching in getServerData. |
Simpler mental model; only GraphQL queries at build time. |
When to Use Server-Side Rendering in Gatsby
Choose SSR for specific scenarios where static generation proves insufficient:
- Personalized Content: Rendering dashboards, account pages, or authenticated experiences where HTML must vary by user session, cookies, or headers.
- Rapidly Changing Data: E-commerce inventory, cryptocurrency prices, or real-time analytics where data changes faster than feasible rebuild intervals.
- Large-Scale Sites: Catalogs with millions of SKUs where pre-building every permutation is build-time prohibitive; SSR generates HTML only for visited pages.
- SEO-Critical Dynamic Pages: When content must be indexable by search engines but requires request-time parameters (e.g., A/B testing variants, geo-specific content).
Summary
- Export
async function getServerData(context)from any page to enable SSR in Gatsby, as implemented inpackages/gatsby/src/utils/get-server-data.ts. - The SSR pipeline in
packages/gatsby/src/utils/page-ssr-module/entry.tsmerges static GraphQL results with runtimeserverDatabefore streaming HTML. - SSR requires Node.js infrastructure and incurs per-request latency but enables real-time data and personalized content.
- SSG remains optimal for static content, offering superior performance through CDN edge caching and simpler deployment to static hosts.
Frequently Asked Questions
What is the difference between getServerData and a static GraphQL query?
Static GraphQL queries execute at build time and produce immutable HTML that is identical for every user. The getServerData function executes on the server at request time, allowing you to access cookies, headers, and query parameters to generate personalized HTML. Both can coexist on the same page—static queries populate the data prop while getServerData populates the serverData prop.
Can I use SSR with Gatsby Cloud or only with self-hosted Node.js servers?
You can deploy SSR pages to Gatsby Cloud, Netlify Functions, Vercel Serverless Functions, or any Node.js environment that supports serverless or traditional server hosting. However, you cannot deploy SSR pages to pure static hosts like GitHub Pages or AWS S3 static website hosting, as these lack Node.js runtime capabilities required to execute getServerData.
How do I handle errors in getServerData to show custom 404 or 500 pages?
Return a status property from getServerData to control the HTTP response code. Return status: 404 to trigger Gatsby's 404 page, or status: 500 for server errors. You can also return an empty props object or error-specific data to render custom error states within your page component. Ensure you wrap external API calls in try-catch blocks to prevent unhandled promise rejections from crashing the server.
Does using SSR affect my site's SEO compared to static generation?
No, SSR preserves SEO benefits because the server returns fully rendered HTML to crawlers, identical to SSG. Search engines can index the content without executing JavaScript. However, SSR pages may have slower Time to First Byte (TTFB) compared to statically cached pages, which could indirectly affect rankings if latency is significant. Use appropriate Cache-Control headers in getServerData to mitigate this by allowing CDN edge caching of SSR responses when personalization is not required.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →