You are viewing documentation for a previous version of this software.

Switch to the latest stable version.

Server-side rendering


Apollo provides two techniques to allow your applications to load quickly, avoiding unnecessary delays to users:

  • Store rehydration, which allows your initial set of queries to return data immediately without a server roundtrip.

  • Server side rendering, which renders the initial HTML view on the server before sending it to the client.

You can use one or both of these techniques to provide a better user experience.

Store rehydration

For applications that can perform some queries on the server prior to rendering the UI on the client, Apollo allows for setting the initial state of data. This is sometimes called rehydration, since the data is "dehydrated" when it is serialized and included in the initial HTML payload.

For example, a typical approach is to include a script tag that looks something like:

html
1<script>
2  window.__APOLLO_STATE__ = JSON.stringify(client.extract());
3</script>

You can then rehydrate the client using the initial state passed from the server:

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

We'll see below how you can generate both the HTML and the Apollo store's state using Node and React Apollo's server rendering functions. However if you are rendering HTML via some other means, you will have to generate the state manually.

Then, when the client runs the first set of queries, the data will be returned instantly because it is already in the store!

If you are using fetchPolicy: network-only or fetchPolicy: cache-and-network on some of the initial queries, you can pass the ssrForceFetchDelay option to skip force fetching during initialization, so that even those queries run using the cache:

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

Server-side rendering

You can render your entire React-based Apollo application on a Node server using rendering functions built into React Apollo. These functions take care of the job of fetching all queries that are required to rendering your component tree. Typically you would use these functions from within a HTTP server such as Express.

No changes are required to client queries to support this, so your Apollo-based React UI should support SSR out of the box.

Server initialization

In order to render your application on the server, you need to handle a HTTP request (using a server like Express, and a server-capable Router like React-Router), and then render your application to a string to pass back on the response.

We'll see how to take your component tree and turn it into a string in the next section, but you'll need to be a little careful in how you construct your Apollo Client instance on the server to ensure everything works there as well:

  1. When creating an Apollo Client instance on the server, you'll need to set up your network interface to connect to the API server correctly. This might look different to how you do it on the client, since you'll probably have to use an absolute URL to the server if you were using a relative URL on the client.

  2. Since you only want to fetch each query result once, pass the ssrMode: true option to the Apollo Client constructor to avoid repeated force-fetching.

  3. You need to ensure that you create a new client or store instance for each request, rather than re-using the same client for multiple requests. Otherwise the UI will be getting stale data and you'll have problems with authentication.

Once you put that all together, you'll end up with initialization code that looks like this:

JavaScript
1// This example uses React Router v4, although it should work
2// equally well with other routers that support SSR
3
4import { ApolloProvider } from '@apollo/react-common';
5import { ApolloClient } from 'apollo-client';
6import { createHttpLink } from 'apollo-link-http';
7import Express from 'express';
8import { StaticRouter } from 'react-router';
9import { InMemoryCache } from "apollo-cache-inmemory";
10
11import Layout from './routes/Layout';
12
13// Note you don't have to use any particular http server, but
14// we're using Express in this example
15const app = new Express();
16app.use((req, res) => {
17
18  const client = new ApolloClient({
19    ssrMode: true,
20    // Remember that this is the interface the SSR server will use to connect to the
21    // API server, so we need to ensure it isn't firewalled, etc
22    link: createHttpLink({
23      uri: 'http://localhost:3010',
24      credentials: 'same-origin',
25      headers: {
26        cookie: req.header('Cookie'),
27      },
28    }),
29    cache: new InMemoryCache(),
30  });
31
32  const context = {};
33
34  // The client-side App will instead use <BrowserRouter>
35  const App = (
36    <ApolloProvider client={client}>
37      <StaticRouter location={req.url} context={context}>
38        <Layout />
39      </StaticRouter>
40    </ApolloProvider>
41  );
42
43  // rendering code (see below)
44});
45
46app.listen(basePort, () => console.log( // eslint-disable-line no-console
47  `app Server is now running on http://localhost:${basePort}`
48));
JavaScript
1// ./routes/Layout.js
2import { Route, Switch } from 'react-router';
3import { Link } from 'react-router-dom';
4import React from 'react';
5
6// A Routes file is a good shared entry-point between client and server
7import routes from './routes';
8
9const Layout = () =>
10  <div>
11    <nav>
12      <ul>
13        <li>
14          <Link to="/">Home</Link>
15        </li>
16        <li>
17          <Link to="/another">Another page</Link>
18        </li>
19      </ul>
20    </nav>
21
22    {/* New <Switch> behavior introduced in React Router v4
23       https://reacttraining.com/react-router/web/api/Switch */}
24    <Switch>
25      {routes.map(route => <Route key={route.name} {...route} />)}
26    </Switch>
27  </div>;
28
29export default Layout;
JavaScript
1// ./routes/index.js
2import MainPage from './MainPage';
3import AnotherPage from './AnotherPage';
4
5const routes = [
6  {
7    path: '/',
8    name: 'home',
9    exact: true,
10    component: MainPage,
11  },
12  {
13    path: '/another',
14    name: 'another',
15    component: AnotherPage,
16  },
17];
18
19export default routes;

Next we'll see what that rendering code actually does.

Using getDataFromTree

The getDataFromTree function takes your React tree, determines which queries are needed to render them, and then fetches them all. It does this recursively down the whole tree if you have nested queries. It returns a promise which resolves when the data is ready in your Apollo Client store.

At the point that the promise resolves, your Apollo Client store will be completely initialized, which should mean your app will now render instantly (since all queries are prefetched) and you can return the stringified results in the response:

JavaScript
1import { getDataFromTree } from "@apollo/react-ssr";
2
3const client = new ApolloClient(....);
4
5// during request (see above)
6getDataFromTree(App).then(() => {
7  // We are ready to render for real
8  const content = ReactDOM.renderToString(App);
9  const initialState = client.extract();
10
11  const html = <Html content={content} state={initialState} />;
12
13  res.status(200);
14  res.send(`<!doctype html>\n${ReactDOM.renderToStaticMarkup(html)}`);
15  res.end();
16});

Your markup in this case can look something like:

JavaScript
1function Html({ content, state }) {
2  return (
3    <html>
4      <body>
5        <div id="root" dangerouslySetInnerHTML={{ __html: content }} />
6        <script dangerouslySetInnerHTML={{
7          __html: `window.__APOLLO_STATE__=${JSON.stringify(state).replace(/</g, '\\u003c')};`,
8        }} />
9      </body>
10    </html>
11  );
12}

Avoiding the network for local queries

If your GraphQL endpoint is on the same server that you're rendering from, you may want to avoid using the network when making your SSR queries. In particular, if localhost is firewalled on your production environment (eg. Heroku), making network requests for these queries will not work.

One solution to this problem is to use an Apollo Link to fetch data using a local graphql schema instead of making a network request. To achieve this, when creating an Apollo Client on the server, you could use SchemaLink instead of using createHttpLink that uses your schema and context to run the query immediately, without any additional network requests.

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

Skipping queries for SSR

If you want to intentionally skip a query during SSR, you can pass ssr: false in the query options. Typically, this will mean the component will get rendered in its loading state on the server. For example:

JavaScript
JavaScript
hooks
1function withClientOnlyUser() {
2  useQuery(GET_USER_WITH_ID, { ssr: false });
3  return <span>I won't be run on the server</span>;
4}

Using renderToStringWithData

The renderToStringWithData function simplifies the above and simply returns the content string that you need to render. So it reduces the number of steps slightly:

JavaScript
1// server application code (integrated usage)
2import { renderToStringWithData } from "@apollo/react-ssr";
3
4const client = new ApolloClient(...);
5
6// during request
7renderToStringWithData(App).then((content) => {
8  const initialState = client.extract();
9  const html = <Html content={content} state={initialState} />;
10
11  res.status(200);
12  res.send(`<!doctype html>\n${ReactDOMServer.renderToStaticMarkup(html)}`);
13  res.end();
14});
Feedback

Edit on GitHub

Forums