React Router framework mode
Integrate Apollo Client with React Router 7
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:
1npm install @apollo/client-integration-react-router @apollo/client graphql rxjsTypeScript 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:
1import { HttpLink, InMemoryCache } from "@apollo/client";
2import {
3 createApolloLoaderHandler,
4 ApolloClient,
5} from "@apollo/client-integration-react-router";
6
7// `request` will be available on the server during SSR or in loaders, but not in the browser
8export const makeClient = (request?: Request) => {
9 return new ApolloClient({
10 cache: new InMemoryCache(),
11 link: new HttpLink({ uri: "https://your-graphql-endpoint.com/graphql" }),
12 });
13};
14
15export const apolloLoader = createApolloLoaderHandler(makeClient);Important:
ApolloClientmust 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:
1npx react-router revealThis 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:
1import { makeClient } from "./apollo";
2import { ApolloProvider } from "@apollo/client";
3import { StrictMode, startTransition } from "react";
4import { hydrateRoot } from "react-dom/client";
5import { HydratedRouter } from "react-router/dom";
6
7startTransition(() => {
8 const client = makeClient();
9 hydrateRoot(
10 document,
11 <StrictMode>
12 <ApolloProvider client={client}>
13 <HydratedRouter />
14 </ApolloProvider>
15 </StrictMode>
16 );
17});Step 4: Configure server entry
Adjust app/entry.server.tsx to wrap your app in ApolloProvider during SSR:
1import { makeClient } from "./apollo";
2import { ApolloProvider } from "@apollo/client";
3// ... other imports
4
5export default function handleRequest(
6 request: Request,
7 responseStatusCode: number,
8 responseHeaders: Headers,
9 routerContext: EntryContext
10) {
11 return new Promise((resolve, reject) => {
12 // ... existing code
13
14 const client = makeClient(request);
15
16 const { pipe, abort } = renderToPipeableStream(
17 <ApolloProvider client={client}>
18 <ServerRouter
19 context={routerContext}
20 url={request.url}
21 abortDelay={ABORT_DELAY}
22 />
23 </ApolloProvider>,
24 {
25 [readyOption]() {
26 shellRendered = true;
27 // ... rest of the handler
28 },
29 // ... other options
30 }
31 );
32 });
33}Step 5: Add hydration helper
Add <ApolloHydrationHelper> to app/root.tsx:
1import { ApolloHydrationHelper } from "@apollo/client-integration-react-router";
2import { Links, Meta, Outlet, Scripts, ScrollRestoration } from "react-router";
3
4export function Layout({ children }: { children: React.ReactNode }) {
5 return (
6 <html lang="en">
7 <head>
8 <meta charSet="utf-8" />
9 <meta name="viewport" content="width=device-width, initial-scale=1" />
10 <Meta />
11 <Links />
12 </head>
13 <body>
14 <ApolloHydrationHelper>{children}</ApolloHydrationHelper>
15 <ScrollRestoration />
16 <Scripts />
17 </body>
18 </html>
19 );
20}
21
22export default function App() {
23 return <Outlet />;
24}Usage
Using apolloLoader with useReadQuery
You can now use the apolloLoader function to create Apollo-enabled loaders for your routes:
1import { gql } from "@apollo/client";
2import { useReadQuery } from "@apollo/client/react";
3import { useLoaderData } from "react-router";
4import type { Route } from "./+types/my-route";
5import type { TypedDocumentNode } from "@apollo/client";
6import { apolloLoader } from "./apollo";
7
8// TypedDocumentNode definition with types
9const GET_USER: TypedDocumentNode<
10 { user: { id: string; name: string; email: string } },
11 { id: string }
12> = gql`
13 query GetUser($id: ID!) {
14 user(id: $id) {
15 id
16 name
17 email
18 }
19 }
20`;
21
22export const loader = apolloLoader<Route.LoaderArgs>()(({ preloadQuery }) => {
23 const userQueryRef = preloadQuery(GET_USER, {
24 variables: { id: "1" },
25 });
26
27 return {
28 userQueryRef,
29 };
30});
31
32export default function UserPage() {
33 const { userQueryRef } = useLoaderData<typeof loader>();
34 const { data } = useReadQuery(userQueryRef);
35
36 return (
37 <div>
38 <h1>{data.user.name}</h1>
39 <p>{data.user.email}</p>
40 </div>
41 );
42}Important: To provide better TypeScript support,
apolloLoaderis 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:
1import { gql } from "@apollo/client";
2import { useReadQuery } from "@apollo/client/react";
3import { useLoaderData } from "react-router";
4import type { Route } from "./+types/my-route";
5import { apolloLoader } from "./apollo";
6
7// TypedDocumentNode definitions omitted for brevity
8
9export const loader = apolloLoader<Route.LoaderArgs>()(({ preloadQuery }) => {
10 const userQueryRef = preloadQuery(GET_USER, {
11 variables: { id: "1" },
12 });
13
14 const postsQueryRef = preloadQuery(GET_POSTS, {
15 variables: { userId: "1" },
16 });
17
18 return {
19 userQueryRef,
20 postsQueryRef,
21 };
22});
23
24export default function UserPage() {
25 const { userQueryRef, postsQueryRef } = useLoaderData<typeof loader>();
26 const { data: userData } = useReadQuery(userQueryRef);
27 const { data: postsData } = useReadQuery(postsQueryRef);
28
29 return (
30 <div>
31 <h1>{userData.user.name}</h1>
32 <h2>Posts</h2>
33 <ul>
34 {postsData.posts.map((post) => (
35 <li key={post.id}>{post.title}</li>
36 ))}
37 </ul>
38 </div>
39 );
40}Important considerations
Import ApolloClient from integration package: Always import
ApolloClientfrom@apollo/client-integration-react-router, not from@apollo/client, to ensure proper SSR hydration.TypeScript support: The
apolloLoaderfunction requires double invocation for proper TypeScript type inference:apolloLoader<LoaderArgs>()(loader).Request context: The
makeClientfunction receives theRequestobject during SSR and in loaders, but not in the browser. Use this to set up auth headers or other request-specific configuration.Streaming SSR: The integration fully supports React's streaming SSR capabilities. Place
Suspenseboundaries strategically for optimal user experience.Cache hydration: The
ApolloHydrationHelpercomponent ensures that data loaded on the server is properly hydrated on the client, preventing unnecessary refetches.