Back to Apollo Client

React Router framework mode

docs/source/integrations/react-router.mdx

3.14.16.9 KB
Original Source

This guide covers integrating Apollo Client in a React Router 7 application with support for modern streaming SSR.

Installation

Install Apollo Client and the React Router integration package:

bash
npm install @apollo/client-integration-react-router @apollo/client graphql rxjs

TypeScript users: For type-safe GraphQL operations, see the GraphQL Codegen guide.

Setup

Step 1: Create Apollo configuration

Create an app/apollo.ts file that exports a makeClient function and an apolloLoader:

typescript
import { HttpLink, InMemoryCache } from "@apollo/client";
import {
  createApolloLoaderHandler,
  ApolloClient,
} from "@apollo/client-integration-react-router";

// `request` will be available on the server during SSR or in loaders, but not in the browser
export const makeClient = (request?: Request) => {
  return new ApolloClient({
    cache: new InMemoryCache(),
    link: new HttpLink({ uri: "https://your-graphql-endpoint.com/graphql" }),
  });
};

export const apolloLoader = createApolloLoaderHandler(makeClient);

Important: ApolloClient must be imported from @apollo/client-integration-react-router, not from @apollo/client.

Step 2: Reveal entry files

Run the following command to create the entry files if they don't exist:

bash
npx react-router reveal

This will create app/entry.client.tsx and app/entry.server.tsx.

Step 3: Configure client entry

Adjust app/entry.client.tsx to wrap your app in ApolloProvider:

typescript
import { makeClient } from "./apollo";
import { ApolloProvider } from "@apollo/client";
import { StrictMode, startTransition } from "react";
import { hydrateRoot } from "react-dom/client";
import { HydratedRouter } from "react-router/dom";

startTransition(() => {
  const client = makeClient();
  hydrateRoot(
    document,
    <StrictMode>
      <ApolloProvider client={client}>
        <HydratedRouter />
      </ApolloProvider>
    </StrictMode>
  );
});

Step 4: Configure server entry

Adjust app/entry.server.tsx to wrap your app in ApolloProvider during SSR:

typescript
import { makeClient } from "./apollo";
import { ApolloProvider } from "@apollo/client";
// ... other imports

export default function handleRequest(
  request: Request,
  responseStatusCode: number,
  responseHeaders: Headers,
  routerContext: EntryContext
) {
  return new Promise((resolve, reject) => {
    // ... existing code

    const client = makeClient(request);

    const { pipe, abort } = renderToPipeableStream(
      <ApolloProvider client={client}>
        <ServerRouter
          context={routerContext}
          url={request.url}
          abortDelay={ABORT_DELAY}
        />
      </ApolloProvider>,
      {
        [readyOption]() {
          shellRendered = true;
          // ... rest of the handler
        },
        // ... other options
      }
    );
  });
}

Step 5: Add hydration helper

Add <ApolloHydrationHelper> to app/root.tsx:

typescript
import { ApolloHydrationHelper } from "@apollo/client-integration-react-router";
import { Links, Meta, Outlet, Scripts, ScrollRestoration } from "react-router";

export function Layout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <Meta />
        <Links />
      </head>
      <body>
        <ApolloHydrationHelper>{children}</ApolloHydrationHelper>
        <ScrollRestoration />
        <Scripts />
      </body>
    </html>
  );
}

export default function App() {
  return <Outlet />;
}

Usage

Using apolloLoader with useReadQuery

You can now use the apolloLoader function to create Apollo-enabled loaders for your routes:

typescript
import { gql } from "@apollo/client";
import { useReadQuery } from "@apollo/client/react";
import { useLoaderData } from "react-router";
import type { Route } from "./+types/my-route";
import type { TypedDocumentNode } from "@apollo/client";
import { apolloLoader } from "./apollo";

// TypedDocumentNode definition with types
const GET_USER: TypedDocumentNode<
  { user: { id: string; name: string; email: string } },
  { id: string }
> = gql`
  query GetUser($id: ID!) {
    user(id: $id) {
      id
      name
      email
    }
  }
`;

export const loader = apolloLoader<Route.LoaderArgs>()(({ preloadQuery }) => {
  const userQueryRef = preloadQuery(GET_USER, {
    variables: { id: "1" },
  });

  return {
    userQueryRef,
  };
});

export default function UserPage() {
  const { userQueryRef } = useLoaderData<typeof loader>();
  const { data } = useReadQuery(userQueryRef);

  return (
    <div>
      <h1>{data.user.name}</h1>
      <p>{data.user.email}</p>
    </div>
  );
}

Important: To provide better TypeScript support, apolloLoader is a method that you need to call twice: apolloLoader<LoaderArgs>()(loader)

Multiple queries in a loader

You can preload multiple queries in a single loader:

typescript
import { gql } from "@apollo/client";
import { useReadQuery } from "@apollo/client/react";
import { useLoaderData } from "react-router";
import type { Route } from "./+types/my-route";
import { apolloLoader } from "./apollo";

// TypedDocumentNode definitions omitted for brevity

export const loader = apolloLoader<Route.LoaderArgs>()(({ preloadQuery }) => {
  const userQueryRef = preloadQuery(GET_USER, {
    variables: { id: "1" },
  });

  const postsQueryRef = preloadQuery(GET_POSTS, {
    variables: { userId: "1" },
  });

  return {
    userQueryRef,
    postsQueryRef,
  };
});

export default function UserPage() {
  const { userQueryRef, postsQueryRef } = useLoaderData<typeof loader>();
  const { data: userData } = useReadQuery(userQueryRef);
  const { data: postsData } = useReadQuery(postsQueryRef);

  return (
    <div>
      <h1>{userData.user.name}</h1>
      <h2>Posts</h2>
      <ul>
        {postsData.posts.map((post) => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
}

Important considerations

  1. Import ApolloClient from integration package: Always import ApolloClient from @apollo/client-integration-react-router, not from @apollo/client, to ensure proper SSR hydration.

  2. TypeScript support: The apolloLoader function requires double invocation for proper TypeScript type inference: apolloLoader<LoaderArgs>()(loader).

  3. Request context: The makeClient function receives the Request object during SSR and in loaders, but not in the browser. Use this to set up auth headers or other request-specific configuration.

  4. Streaming SSR: The integration fully supports React's streaming SSR capabilities. Place Suspense boundaries strategically for optimal user experience.

  5. Cache hydration: The ApolloHydrationHelper component ensures that data loaded on the server is properly hydrated on the client, preventing unnecessary refetches.