Next.js App Router
Integrate Apollo Client in Next.js apps
This guide covers integrating Apollo Client in a Next.js application using the App Router architecture with support for both React Server Components (RSC) and Client Components.
What is supported?
React Server Components
Apollo Client provides a shared client instance across all server components for a single request, preventing duplicate GraphQL requests and optimizing server-side rendering.
React Client Components
When using the app directory, client components are rendered both on the server (SSR) and in the browser. Apollo Client enables you to execute GraphQL queries on the server and use the results to hydrate your browser-side cache, delivering fully-rendered pages to users.
Installation
Install Apollo Client and the Next.js integration package:
1npm install @apollo/client@latest @apollo/client-integration-nextjs graphql rxjsTypeScript users: For type-safe GraphQL operations, see the GraphQL Codegen guide.
Setup for React Server Components (RSC)
Step 1: Create Apollo Client configuration
Create an ApolloClient.ts file in your app directory:
1import { HttpLink } from "@apollo/client";
2import {
3 registerApolloClient,
4 ApolloClient,
5 InMemoryCache,
6} from "@apollo/client-integration-nextjs";
7
8export const { getClient, query, PreloadQuery } = registerApolloClient(() => {
9 return new ApolloClient({
10 cache: new InMemoryCache(),
11 link: new HttpLink({
12 // Use an absolute URL for SSR (relative URLs cannot be used in SSR)
13 uri: "https://your-api.com/graphql",
14 fetchOptions: {
15 // Optional: Next.js-specific fetch options for caching and revalidation
16 // See: https://nextjs.org/docs/app/api-reference/functions/fetch
17 },
18 }),
19 });
20});Step 2: Use in server components
You can now use the getClient function or the query shortcut in your server components:
1import { query } from "./ApolloClient";
2
3async function UserProfile({ userId }: { userId: string }) {
4 const { data } = await query({
5 query: GET_USER,
6 variables: { id: userId },
7 });
8
9 return <div>{data.user.name}</div>;
10}Override Next.js fetch options
You can override Next.js-specific fetch options per query using context.fetchOptions:
1const { data } = await getClient().query({
2 query: GET_USER,
3 context: {
4 fetchOptions: {
5 next: { revalidate: 60 }, // Revalidate every 60 seconds
6 },
7 },
8});Setup for Client Components (SSR and browser)
Step 1: Create Apollo wrapper component
Create app/ApolloWrapper.tsx:
1"use client";
2
3import { HttpLink } from "@apollo/client";
4import {
5 ApolloNextAppProvider,
6 ApolloClient,
7 InMemoryCache,
8} from "@apollo/client-integration-nextjs";
9
10function makeClient() {
11 const httpLink = new HttpLink({
12 // Use an absolute URL for SSR
13 uri: "https://your-api.com/graphql",
14 fetchOptions: {
15 // Optional: Next.js-specific fetch options
16 // Note: This doesn't work with `export const dynamic = "force-static"`
17 },
18 });
19
20 return new ApolloClient({
21 cache: new InMemoryCache(),
22 link: httpLink,
23 });
24}
25
26export function ApolloWrapper({ children }: React.PropsWithChildren) {
27 return (
28 <ApolloNextAppProvider makeClient={makeClient}>
29 {children}
30 </ApolloNextAppProvider>
31 );
32}Step 2: Wrap root layout
Wrap your RootLayout in the ApolloWrapper component in app/layout.tsx:
1import { ApolloWrapper } from "./ApolloWrapper";
2
3export default function RootLayout({
4 children,
5}: {
6 children: React.ReactNode;
7}) {
8 return (
9 <html lang="en">
10 <body>
11 <ApolloWrapper>{children}</ApolloWrapper>
12 </body>
13 </html>
14 );
15}Note: This works even if your layout is a React Server Component. It ensures all Client Components share the same Apollo Client instance through
ApolloNextAppProvider.
Step 3: Use Apollo Client hooks in client components
For optimal streaming SSR, use suspense-enabled hooks like useSuspenseQuery and useFragment:
1"use client";
2
3import { useSuspenseQuery } from "@apollo/client/react";
4
5export function UserProfile({ userId }: { userId: string }) {
6 const { data } = useSuspenseQuery(GET_USER, {
7 variables: { id: userId },
8 });
9
10 return <div>{data.user.name}</div>;
11}Preloading data from RSC to client components
You can preload data in React Server Components to populate the cache of your Client Components.
Step 1: Use PreloadQuery in server components
1import { PreloadQuery } from "./ApolloClient";
2import { Suspense } from "react";
3
4export default async function Page() {
5 return (
6 <PreloadQuery query={GET_USER} variables={{ id: "1" }}>
7 <Suspense fallback={<>Loading...</>}>
8 <ClientChild />
9 </Suspense>
10 </PreloadQuery>
11 );
12}Step 2: Consume with useSuspenseQuery in client components
1"use client";
2
3import { useSuspenseQuery } from "@apollo/client/react";
4
5export function ClientChild() {
6 const { data } = useSuspenseQuery(GET_USER, {
7 variables: { id: "1" },
8 });
9
10 return <div>{data.user.name}</div>;
11}Important: Data fetched this way should be considered client data and never referenced in Server Components.
PreloadQueryprevents mixing server data and client data by creating a separateApolloClientinstance.
Using with useReadQuery
For advanced use cases, you can use PreloadQuery with useReadQuery to avoid request waterfalls:
1<PreloadQuery query={GET_USER} variables={{ id: "1" }}>
2 {(queryRef) => (
3 <Suspense fallback={<>Loading...</>}>
4 <ClientChild queryRef={queryRef} />
5 </Suspense>
6 )}
7</PreloadQuery>;In your Client Component:
1"use client";
2
3import {
4 useQueryRefHandlers,
5 useReadQuery,
6 QueryRef,
7} from "@apollo/client/react";
8
9export function ClientChild({ queryRef }: { queryRef: QueryRef<TQueryData> }) {
10 const { refetch } = useQueryRefHandlers(queryRef);
11 const { data } = useReadQuery(queryRef);
12
13 return <div>{data.user.name}</div>;
14}Handling multipart responses (@defer) in SSR
When using the @defer directive, useSuspenseQuery will only suspend until the initial response is received. To handle deferred data properly, you have three strategies:
Strategy 1: Use PreloadQuery with useReadQuery
PreloadQuery allows deferred data to be fully transported and streamed chunk-by-chunk.
Strategy 2: Remove @defer fragments
Use RemoveMultipartDirectivesLink to strip @defer directives from queries during SSR:
1import { RemoveMultipartDirectivesLink } from "@apollo/client-integration-nextjs";
2
3new RemoveMultipartDirectivesLink({
4 stripDefer: true, // Default: true
5});You can exclude specific fragments from stripping by labeling them:
1query myQuery {
2 fastField
3 ... @defer(label: "SsrDontStrip1") {
4 slowField1
5 }
6}Strategy 3: Wait for deferred data
Use AccumulateMultipartResponsesLink to debounce the initial response:
1import { AccumulateMultipartResponsesLink } from "@apollo/client-integration-nextjs";
2
3new AccumulateMultipartResponsesLink({
4 cutoffDelay: 100, // Wait up to 100ms for incremental data
5});Combined approach: SSRMultipartLink
Combine both strategies with SSRMultipartLink:
1import { SSRMultipartLink } from "@apollo/client-integration-nextjs";
2
3new SSRMultipartLink({
4 stripDefer: true,
5 cutoffDelay: 100,
6});Testing
Reset singleton instances between tests using the resetApolloClientSingletons helper:
1import { resetApolloClientSingletons } from "@apollo/client-integration-nextjs";
2
3afterEach(resetApolloClientSingletons);Debugging
Enable verbose logging in your app/ApolloWrapper.tsx:
1import { setLogVerbosity } from "@apollo/client";
2
3setLogVerbosity("debug");Important considerations
Separate RSC and SSR queries: Avoid overlapping queries between RSC and SSR. RSC queries don't update in the browser, while SSR queries can update dynamically as the cache changes.
Use absolute URLs: Always use absolute URLs in
HttpLinkfor SSR, as relative URLs cannot be used in server-side rendering.Streaming SSR: For optimal performance, use
useSuspenseQueryanduseFragmentto take advantage of React 18's streaming SSR capabilities.Suspense boundaries: Place
Suspenseboundaries at meaningful places in your UI for the best user experience.