or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

components.mdhoc.mdhooks.mdindex.mdssr.mdtesting.md
tile.json

ssr.mddocs/

React Apollo Server-Side Rendering

React Apollo SSR utilities enable server-side rendering of GraphQL-powered React applications with complete data fetching before HTML generation.

Capabilities

getDataFromTree

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');
  }
});

getMarkupFromTree

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');
  }
});

renderToStringWithData

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
    };
  }
}

SSR Best Practices

Client Hydration

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'));

Error Handling

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');
    }
  }
});

Performance Optimization

// 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);
}