# How to Implement Server-Side Rendering (SSR) in Gatsby: Complete Guide with Trade-offs

> Implement Server-Side Rendering in Gatsby by exporting getServerData. Fetch fresh data on each request and stream HTML to the client. Understand Gatsby SSR trade-offs.

- Repository: [Gatsby/gatsby](https://github.com/gatsbyjs/gatsby)
- Tags: tutorial
- Published: 2026-03-06

---

**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`](https://github.com/gatsbyjs/gatsby/blob/main/packages/gatsby/src/utils/page-ssr-module/entry.ts), executes the following sequence on every request:

1. Resolves the page component and runs any **build-time GraphQL page query** (the SSG portion).
2. Invokes the exported `getServerData` function from [`packages/gatsby/src/utils/get-server-data.ts`](https://github.com/gatsbyjs/gatsby/blob/main/packages/gatsby/src/utils/get-server-data.ts) to fetch request-specific data.
3. Merges the static query result (`data`) with the runtime result (`serverData`) into the page props.
4. 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.

```javascript
// 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`](https://github.com/gatsbyjs/gatsby/blob/main/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.

```javascript
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`](https://github.com/gatsbyjs/gatsby/blob/main/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.

```javascript
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 in [`packages/gatsby/src/utils/get-server-data.ts`](https://github.com/gatsbyjs/gatsby/blob/main/packages/gatsby/src/utils/get-server-data.ts).
- The SSR pipeline in [`packages/gatsby/src/utils/page-ssr-module/entry.ts`](https://github.com/gatsbyjs/gatsby/blob/main/packages/gatsby/src/utils/page-ssr-module/entry.ts) merges static GraphQL results with runtime `serverData` before 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.