React Apollo SSR utilities enable server-side rendering of GraphQL-powered React applications with complete data fetching before HTML generation.
Walks a React element tree and executes all GraphQL queries, returning markup with data preloaded.
/**
* Walk React tree and execute GraphQL queries for SSR
* @param tree - React element tree to process
* @param context - Optional context object for the tree walk
* @returns Promise resolving to HTML string with data
*/
function getDataFromTree(
tree: React.ReactNode,
context?: { [key: string]: any }
): Promise<string>;Usage Examples:
import React from "react";
import { ApolloProvider } from "react-apollo";
import { getDataFromTree } from "react-apollo";
import { renderToString } from "react-dom/server";
// Express.js server example
app.get('*', async (req, res) => {
const client = new ApolloClient({
ssrMode: true,
cache: new InMemoryCache(),
link: createHttpLink({
uri: 'http://localhost:4000/graphql',
credentials: 'same-origin',
headers: {
cookie: req.header('Cookie'),
},
}),
});
const App = (
<ApolloProvider client={client}>
<Router location={req.url}>
<Routes>
<Route path="/" component={HomePage} />
<Route path="/users" component={UsersPage} />
</Routes>
</Router>
</ApolloProvider>
);
try {
// This will execute all GraphQL queries in the component tree
await getDataFromTree(App);
// Render to string after data is loaded
const content = renderToString(App);
const initialState = client.extract();
const html = `
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
</head>
<body>
<div id="root">${content}</div>
<script>
window.__APOLLO_STATE__ = ${JSON.stringify(initialState).replace(/</g, '\\u003c')};
</script>
<script src="/static/js/app.js"></script>
</body>
</html>
`;
res.send(html);
} catch (error) {
console.error('SSR Error:', error);
res.status(500).send('Server Error');
}
});More configurable version of getDataFromTree that allows custom render functions.
/**
* Walk React tree with custom render function for SSR
* @param options - Configuration object with tree, context, and render function
* @returns Promise resolving to HTML string with data
*/
function getMarkupFromTree(options: GetMarkupFromTreeOptions): Promise<string>;
interface GetMarkupFromTreeOptions {
tree: React.ReactNode;
context?: { [key: string]: any };
renderFunction?: (tree: React.ReactElement<any>) => string;
}Usage Examples:
import React from "react";
import { getMarkupFromTree } from "react-apollo";
import { renderToString } from "react-dom/server";
import { renderToStaticMarkup } from "react-dom/server";
// Custom SSR with static markup
async function renderAppStatic(App) {
const markup = await getMarkupFromTree({
tree: App,
renderFunction: renderToStaticMarkup, // No React hydration attributes
});
return markup;
}
// Custom SSR with context
async function renderAppWithContext(App, serverContext) {
const markup = await getMarkupFromTree({
tree: App,
context: {
...serverContext,
isServer: true,
userAgent: req.headers['user-agent']
},
renderFunction: (tree) => {
// Custom rendering logic
return renderToString(tree);
}
});
return markup;
}
// Advanced SSR example with error handling
app.get('*', async (req, res) => {
const client = new ApolloClient({
ssrMode: true,
cache: new InMemoryCache(),
link: createHttpLink({
uri: process.env.GRAPHQL_ENDPOINT,
fetch: fetch,
}),
});
const context = {};
const App = (
<ApolloProvider client={client}>
<StaticRouter location={req.url} context={context}>
<App />
</StaticRouter>
</ApolloProvider>
);
try {
const markup = await getMarkupFromTree({
tree: App,
context: {
request: req,
response: res
},
renderFunction: (tree) => {
const html = renderToString(tree);
// Handle redirects
if (context.url) {
res.redirect(301, context.url);
return '';
}
return html;
}
});
if (context.url) {
return; // Redirect handled above
}
const initialState = client.extract();
res.send(generateHTML(markup, initialState));
} catch (error) {
console.error('SSR Error:', error);
res.status(500).send('Internal Server Error');
}
});Convenience function that combines data fetching and rendering in a single call.
/**
* Render React component to string with GraphQL data preloaded
* @param component - React element to render
* @returns Promise resolving to HTML string with data
*/
function renderToStringWithData(
component: React.ReactElement<any>
): Promise<string>;Usage Examples:
import React from "react";
import { ApolloProvider } from "react-apollo";
import { renderToStringWithData } from "react-apollo";
// Simple SSR example
app.get('*', async (req, res) => {
const client = new ApolloClient({
ssrMode: true,
cache: new InMemoryCache(),
link: createHttpLink({
uri: 'http://localhost:4000/graphql',
}),
});
const App = (
<ApolloProvider client={client}>
<Router location={req.url}>
<Switch>
<Route exact path="/" component={HomePage} />
<Route path="/profile/:id" component={ProfilePage} />
</Switch>
</Router>
</ApolloProvider>
);
try {
// This will fetch data and render in one step
const content = await renderToStringWithData(App);
const initialState = client.extract();
const html = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>My GraphQL App</title>
<link rel="stylesheet" href="/static/css/app.css">
</head>
<body>
<div id="root">${content}</div>
<script>
window.__APOLLO_STATE__ = ${JSON.stringify(initialState).replace(/</g, '\\u003c')};
</script>
<script src="/static/js/app.js"></script>
</body>
</html>
`;
res.send(html);
} catch (error) {
console.error('SSR failed:', error);
res.status(500).send('Server Error');
}
});
// Next.js page example
export async function getServerSideProps(context) {
const client = new ApolloClient({
ssrMode: true,
cache: new InMemoryCache(),
link: createHttpLink({
uri: process.env.GRAPHQL_ENDPOINT,
}),
});
const App = (
<ApolloProvider client={client}>
<UserProfile userId={context.params.id} />
</ApolloProvider>
);
try {
await renderToStringWithData(App);
return {
props: {
initialApolloState: client.extract(),
userId: context.params.id
}
};
} catch (error) {
return {
notFound: true
};
}
}import React from "react";
import { hydrate } from "react-dom";
import { ApolloProvider } from "react-apollo";
import { InMemoryCache } from "apollo-cache-inmemory";
// Client-side hydration
function createApolloClient() {
return new ApolloClient({
ssrForceFetchDelay: 100, // Avoid duplicate fetches
cache: new InMemoryCache().restore(window.__APOLLO_STATE__),
link: createHttpLink({
uri: '/graphql',
credentials: 'same-origin',
}),
});
}
const client = createApolloClient();
const App = (
<ApolloProvider client={client}>
<Router>
<Routes />
</Router>
</ApolloProvider>
);
hydrate(App, document.getElementById('root'));import { ApolloError } from "apollo-client";
app.get('*', async (req, res) => {
try {
const content = await renderToStringWithData(App);
res.send(generateHTML(content, client.extract()));
} catch (error) {
if (error instanceof ApolloError) {
// GraphQL errors
console.error('GraphQL SSR Error:', error.graphQLErrors);
res.status(500).send('GraphQL Error');
} else {
// Network or other errors
console.error('SSR Error:', error);
res.status(500).send('Server Error');
}
}
});// Cache Apollo client instances
const clientCache = new Map();
function getApolloClient(req) {
const cacheKey = req.headers.authorization || 'anonymous';
if (!clientCache.has(cacheKey)) {
const client = new ApolloClient({
ssrMode: true,
cache: new InMemoryCache(),
link: createHttpLink({
uri: process.env.GRAPHQL_ENDPOINT,
headers: {
authorization: req.headers.authorization,
},
}),
});
clientCache.set(cacheKey, client);
}
return clientCache.get(cacheKey);
}