UNPKG

@apollo/client

Version:

A fully-featured caching GraphQL client.

257 lines (200 loc) 7.12 kB
# Apollo Client Integration with React Router Framework Mode 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 [TypeScript Code Generation guide](typescript-codegen.md). ## 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.