Thanks to visit codestin.com
Credit goes to www.apollographql.com

Server-side rendering


note
This page describes manual server side rendering setups with React. If you are using a modern SSR-focused framework like Next.js, React Router Framework mode, or TanStack Start, you need to use one of our Apollo Client Framework integrations instead. You can find these integrations on GitHub:

Server-side rendering (SSR) is a performance optimization for modern web apps. It enables you to render your app's initial state to raw HTML and CSS on the server before serving it to a browser. This means users don't have to wait for their browser to download and initialize React (or Angular, Vue, etc.) before content is available:

Apollo Client provides a handy API for using it with server-side rendering, including a function that executes all of the GraphQL queries that are required to render your component tree. You don't need to make any changes to your queries to support this API.

Differences from client-side rendering

When you render your React app on the server side, most of the code is identical to its client-side counterpart, with a few important exceptions:

  • You need to use a server-compatible router for React, such as React Router.

    (In the case of React Router, you wrap your application in a StaticRouter component instead of the BrowserRouter you use on the client side.)

  • You need to replace relative URLs with absolute URLs wherever applicable.

  • The initialization of Apollo Client changes slightly, as described below.

Initializing Apollo Client

Here's an example server-side initialization of Apollo Client:

JavaScript
1import { ApolloClient, HttpLink, InMemoryCache } from "@apollo/client";
2
3const client = new ApolloClient({
4  ssrMode: true,
5  link: new HttpLink({
6    uri: "http://localhost:3010",
7    credentials: "same-origin",
8    headers: {
9      cookie: req.header("Cookie"),
10    },
11  }),
12  cache: new InMemoryCache(),
13});

Provide ssrMode: true to prevent Apollo Client from polling on the server. This setting also tells the client to prioritize cache values over network requests when possible.

You also might need to configure your GraphQL endpoint to accept GraphQL operations from your SSR server (for example, by safelisting its domain or IP). Use absolute URLs for your GraphQL endpoint on the server, because relative network requests can only be made in a browser.

It's possible and valid for your GraphQL endpoint to be hosted by the same server that's performing SSR. In this case, Apollo Client doesn't need to make network requests to execute queries. For details, see Avoiding the network for local queries.

Example

Let's look at an example of SSR in a Node.js app. This example uses Express and React Router v4, although it can work with any server middleware and any router that supports SSR.

First, here's an example app.js file, without the code for rendering React to HTML and CSS:

Click to expand
JavaScript
app.js
1import { ApolloClient, HttpLink, InMemoryCache } from "@apollo/client";
2import { ApolloProvider } from "@apollo/client/react";
3import Express from "express";
4import React from "react";
5import { StaticRouter } from "react-router";
6
7// File shown below
8import Layout from "./routes/Layout";
9
10const app = new Express();
11app.use((req, res) => {
12  const client = new ApolloClient({
13    ssrMode: true,
14    link: new HttpLink({
15      uri: "http://localhost:3010",
16      credentials: "same-origin",
17      headers: {
18        cookie: req.header("Cookie"),
19      },
20    }),
21    cache: new InMemoryCache(),
22  });
23
24  const context = {};
25
26  // The client-side App will instead use <BrowserRouter>
27  const App = (
28    <ApolloProvider client={client}>
29      <StaticRouter location={req.url} context={context}>
30        <Layout />
31      </StaticRouter>
32    </ApolloProvider>
33  );
34
35  // TODO: rendering code (see below)
36});
37
38app.listen(basePort, () =>
39  console.log(`app Server is now running on http://localhost:${basePort}`)
40);

So far, whenever this example server receives a request, it first initializes Apollo Client and then creates a React tree that's wrapped with the ApolloProvider and StaticRouter components. The contents of that tree depend on the request's path and the StaticRouter's defined routes.

caution
It's important to create an entirely new instance of Apollo Client for each request. Otherwise, your response to a request might include sensitive cached query results from a previous request.

Executing queries with prerenderStatic

You can instruct Apollo Client to execute all of the queries executed by the useQuery or the suspenseful query hooks (like useSuspenseQuery and useBackgroundQuery) in the React tree's components with the prerenderStatic function.

This function rerenders your React tree until no more network requests are made. When you use suspenseful hooks with a suspense-ready rendering function, the tree is rendered once and suspends while network requests are executed. When you use non-suspenseful hooks (like useQuery), this function renders all components, waits for all requests to finish, and then re-renders the tree until no more requests are made.

The function returns a Promise that resolves when all result data is ready in the Apollo Client cache and the final render is complete.

Choosing a rendering function

prerenderStatic supports multiple React rendering functions:

  • prerender from react-dom/static - Recommended for Deno or modern edge runtimes with Web Streams. Supports React Suspense.

  • prerenderToNodeStream from react-dom/static - Recommended for Node.js. Supports React Suspense.

  • renderToString from react-dom/server - Legacy API without Suspense support. Won't work with suspenseful hooks.

  • renderToStaticMarkup from react-dom/server - Legacy API without Suspense support. Slightly faster than renderToString, but the result cannot be hydrated.

The following code replaces the TODO comment within the app.use call in the example above:

JavaScript
app.js
1// Add these imports to the top of the file
2import { prerenderStatic } from "@apollo/client/react/ssr";
3import { prerenderToNodeStream } from "react-dom/static";
4
5// Replace the TODO with this
6prerenderStatic({
7  tree: App,
8  // this is optional if your `App` component contains an <ApolloProvider>
9  context: { client },
10  renderFunction: prerenderToNodeStream,
11}).then(async ({ result }) => {
12  // Extract the entirety of the Apollo Client cache's current state
13  const initialState = client.extract();
14
15  // TODO: Send the response to the client (see below for examples)
16});

Sending the response

After prerenderStatic completes, you need to send the HTML response to the client. The approach depends on the rendering function you chose.

Using streaming with renderToPipeableStream

For Node.js environments, you can stream the response to the client using renderToPipeableStream. This allows the browser to start displaying content before the entire page is rendered:

JavaScript
app.js
1import { renderToPipeableStream } from "react-dom/server";
2
3// After prerenderStatic completes
4prerenderStatic({
5  tree: App,
6  context: { client },
7  renderFunction: prerenderToNodeStream,
8}).then(async ({ result }) => {
9  const initialState = client.extract();
10
11  // Render the app again with streaming, injecting the Apollo state
12  const { pipe } = renderToPipeableStream(
13    <html>
14      <head>
15        <title>My App</title>
16      </head>
17      <body>
18        <div id="root">
19          <App />
20        </div>
21      </body>
22    </html>,
23    {
24      bootstrapScriptContent: `window.__APOLLO_STATE__=${JSON.stringify(
25        initialState
26      ).replace(/</g, "\\u003c")}`,
27      bootstrapScripts: ["/client.js"],
28      onShellReady() {
29        // Start streaming the response to the browser
30        res.setHeader("Content-Type", "text/html");
31        res.statusCode = 200;
32        pipe(res);
33      },
34      onError(error) {
35        console.error("Rendering error:", error);
36      },
37    }
38  );
39});
note
Instead of rendering <div id="root"><App /></div>, you can also render <div id="root" dangerouslySetInnerHTML={{ __html: result }} /> to avoid rendering the entire tree twice. However, if you go this approach, React cannot decide on the order in which components are streamed to the browser, which might be suboptimal. In the end, it's a tradeoff between more work on the server and a potential delay in displaying content in the browser that you need to make based on your individual requirements.
tip
If you are only using suspenseful hooks (like useSuspenseQuery or useBackgroundQuery) and no useQuery hooks, is also possible to use the @apollo/client-react-streaming package to stream data dynamically while it is fetching, without requiring a first render pass with prerenderStatic. You can find an example application that uses this approach in the integration tests for that package.
note
The replace call in these examples escapes the < character to prevent cross-site scripting attacks that are possible via the presence of </script> in a string literal.
Using renderToString for synchronous rendering

For simpler use cases or environments without stream support, you can use renderToString to render the entire page synchronously:

JavaScript
app.js
1import { renderToString } from "react-dom/server";
2
3// After prerenderStatic completes
4prerenderStatic({
5  tree: App,
6  context: { client },
7  renderFunction: renderToString,
8}).then(async ({ result }) => {
9  const initialState = client.extract();
10
11  // Create a complete HTML document with the cache state
12  const Html = () => (
13    <html>
14      <head>
15        <title>My App</title>
16      </head>
17      <body>
18        <div id="root" dangerouslySetInnerHTML={{ __html: result }} />
19        <script
20          dangerouslySetInnerHTML={{
21            __html: `window.__APOLLO_STATE__=${JSON.stringify(
22              initialState
23            ).replace(/</g, "\\u003c")};`,
24          }}
25        />
26        <script src="/client.js" />
27      </body>
28    </html>
29  );
30
31  // Render to string and send
32  const html = renderToString(<Html />);
33
34  res.setHeader("Content-Type", "text/html");
35  res.status(200);
36  res.send(`<!DOCTYPE html>${html}`);
37  res.end();
38});
note
The replace call in these examples escapes the < character to prevent cross-site scripting attacks that are possible via the presence of </script> in a string literal.

Advanced options

prerenderStatic provides several options to customize the rendering process:

Diagnostics

You can enable diagnostics to detect inefficient rendering structures (like useQuery waterfalls) in your app:

JavaScript
1const { diagnostics } = await prerenderStatic({
2  tree: App,
3  context: { client },
4  renderFunction: prerenderToNodeStream,
5  diagnostics: true,
6});
7
8console.log(`Rendered ${diagnostics.renderCount} times`);
9// If renderCount is high, consider using fragment colocation

Timeout support

You can use an AbortSignal to stop the render loop early:

JavaScript
1const signal = AbortSignal.timeout(2000); // 2 second timeout
2
3const { result, aborted } = await prerenderStatic({
4  tree: App,
5  context: { client },
6  renderFunction: (tree) => prerenderToNodeStream(tree, { signal }),
7  signal,
8});
9
10if (aborted) {
11  console.log("Render timed out, returning partial result");
12}
Maximum rerenders

If you have deep useQuery waterfalls, you can increase the maxRerenders option (default: 50):

JavaScript
1await prerenderStatic({
2  tree: App,
3  context: { client },
4  renderFunction: prerenderToNodeStream,
5  maxRerenders: 100,
6});

Rehydrating the client-side cache

Although the server-side cache's state is available in __APOLLO_STATE__, it isn't yet available in the client-side cache. InMemoryCache provides a helpful restore function for rehydrating its state with data extracted from another cache instance.

In your client-side initialization of Apollo Client, you can rehydrate the cache like so:

JavaScript
1const client = new ApolloClient({
2  cache: new InMemoryCache().restore(window.__APOLLO_STATE__),
3  uri: "https://example.com/graphql",
4});

Now when the client-side version of the app runs its initial queries, the data is returned instantly because it's already in the cache!

Overriding fetch policies during initialization

If some of your initial queries use the network-only or cache-and-network fetch policy, you can provide the ssrForceFetchDelay option to Apollo Client to skip force-fetching those queries during initialization. This way, even those queries initially run using only the cache:

JavaScript
1const client = new ApolloClient({
2  cache: new InMemoryCache().restore(window.__APOLLO_STATE__),
3  link,
4  ssrForceFetchDelay: 100, // in milliseconds
5});

Avoiding the network for local queries

If your GraphQL endpoint is hosted by the same server that you're rendering from, you can optionally avoid using the network when executing your SSR queries. This is particularly helpful if localhost is firewalled in the server's environment (e.g., on Heroku).

When creating an Apollo Client on the server, use a SchemaLink instead of an HttpLink. SchemaLink uses your schema and context to run the query immediately, without the need for a network request:

JavaScript
1import { ApolloClient, InMemoryCache } from "@apollo/client";
2import { SchemaLink } from "@apollo/client/link/schema";
3
4// ...
5
6const client = new ApolloClient({
7  // Instead of HttpLink use SchemaLink here
8  link: new SchemaLink({ schema }),
9  cache: new InMemoryCache(),
10});

Selectively disabling query execution during SSR

If you want to prevent a particular query from executing during SSR, use ssr: false in that query's options. This is useful for queries that should only run on the client side, such as user-specific data that isn't available during SSR.

When ssr: false is set, the component receives a result with loading: true, dataState: "empty", and data: undefined during server-side rendering. The query will execute normally once the component hydrates on the client.

JavaScript
1function ClientOnlyUser() {
2  const { loading, data } = useQuery(GET_USER_WITH_ID, { ssr: false });
3
4  if (loading) {
5    return <span>Loading...</span>;
6  }
7
8  return <span>User: {data?.user?.name || "Not loaded"}</span>;
9}
note
ssr: false behaves differently than skip: true, which prevents the query from executing on both the server and client until skip is set to false. During SSR, skip: true results in loading: false and networkStatus: NetworkStatus.ready.

Legacy APIs

Apollo Client provides two legacy SSR functions that are both replaced by prerenderStatic:

  • getDataFromTree - Executes all queries in a component tree and returns when data is ready. Uses renderToStaticMarkup by default.

  • renderToStringWithData - Similar to getDataFromTree, but returns the rendered string using renderToString.

These functions are deprecated. Use prerenderStatic instead, which offers better flexibility and performance with modern React rendering APIs.

Feedback

Edit on GitHub

Ask Community