Apollo 22: SSR with Next.js
easy⏱ 5 mincourseapollo
Apollo in Next.js App Router
Next.js App Router uses React Server Components (RSC) by default. Apollo works in this model with two patterns:
- Server Components: Use
getClient() from @apollo/experimental-nextjs-app-support to create a server-side Apollo Client. Call client.query() directly — no hooks, no providers.
- Client Components: Wrap your tree with
ApolloNextAppProvider and use hooks (useQuery, useSuspenseQuery) normally.
The key challenge is hydration: data fetched on the server must be passed to the client without re-fetching. The @apollo/experimental-nextjs-app-support package handles this via cache serialization and rehydration.
// lib/apollo-client.ts — Server-side client
import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';
import { registerApolloClient, ApolloClient as RSCApolloClient } from '@apollo/experimental-nextjs-app-support';
export const { getClient } = registerApolloClient(() =>
new ApolloClient({
cache: new InMemoryCache(),
link: new HttpLink({
uri: process.env.GRAPHQL_ENDPOINT,
}),
})
);
Server-side query in RSC
Create a getClient() function using registerApolloClient from @apollo/experimental-nextjs-app-support. In a Server Component (page.tsx), call const { data } = await getClient().query({ query: GET_POSTS }). Render the posts directly — no loading states needed since this runs on the server. For interactive parts, create a Client Component ('use client') wrapped in ApolloNextAppProvider that uses useSuspenseQuery for real-time updates.
Avoiding hydration mismatch
Hydration mismatches occur when server-rendered HTML doesn't match client-rendered HTML. With Apollo, this happens when:
- The server and client use different cache states — the client re-fetches and gets different data.
Date.now() or random values are used in queries.
- The server has different environment variables.
Fix: use ApolloNextAppProvider which automatically serializes the server cache and rehydrates it on the client. Always use useSuspenseQuery instead of useQuery in SSR — it integrates with React's streaming architecture.
// app/layout.tsx — Provider for client components
'use client';
import { ApolloNextAppProvider } from '@apollo/experimental-nextjs-app-support';
import { makeClient } from './lib/apollo-client';
export function ApolloWrapper({ children }) {
return (
<ApolloNextAppProvider makeClient={makeClient}>
{children}
</ApolloNextAppProvider>
);
}
Streaming with Suspense
Next.js App Router supports streaming: the server sends HTML progressively as data becomes available. Combine this with Apollo's useSuspenseQuery for optimal UX:
- The server renders the shell immediately.
- Components using
useSuspenseQuery show Suspense fallbacks.
- As each query resolves on the server, the HTML chunk is streamed to the client.
- The client hydrates each chunk as it arrives.
This gives you fast initial paint (TTFB) with progressive data loading — the best of both SSR and client-side fetching.