Lesson 61 of 81
A Server Component can fetch GraphQL with nothing but fetch() and get Next.js's own request cache for free. A Client Component using Apollo or urql gets a completely different cache instead — and the two don't know about each other.
15 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
A GraphQL request is just an HTTP POST with a JSON body — there's nothing about it that requires a GraphQL client library. In a Server Component, that means you can call fetch() directly against your GraphQL endpoint, the exact same fetch() this track's caching lesson already covers, and get Next.js's request-deduping and data-cache behavior on it, same as any other fetch.
A Client Component is a different story. Client-side GraphQL almost always goes through a client library — Apollo Client or urql are the common ones — because the interesting part of client-side GraphQL isn't the network call, it's the normalized cache these libraries maintain: every object returned by any query gets stored once, keyed by its type and ID, so that updating it in one place updates every component reading it anywhere else in the app, no manual cache-busting required.
Those are two separate caching systems with zero awareness of each other. Next.js's fetch cache lives on the server and keys on the request itself; Apollo/urql's normalized cache lives in the browser and keys on the GraphQL types and IDs it parsed out of the response. A Server Component's fetch and a Client Component's Apollo query hitting the same underlying data can each show a different, independently-stale answer, and neither cache has any way to know the other exists.
Server Component, plain fetch, opted into Next.js's cache explicitly:
// app/products/[id]/page.tsx
async function getProduct(id: string) {
const res = await fetch("https://api.example.com/graphql", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
query: `query Product($id: ID!) { product(id: $id) { name price } }`,
variables: { id },
}),
next: { revalidate: 60 }, // opt in — POST isn't cached by default
});
const { data } = await res.json();
return data.product;
}
export default async function ProductPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
const product = await getProduct(id);
return <h1>{product.name} — ${product.price}</h1>;
}
Client Component, Apollo, with its own independent cache:
// app/products/[id]/price-badge.tsx
"use client";
import { useQuery, gql } from "@apollo/client";
const PRODUCT_PRICE = gql`
query ProductPrice($id: ID!) {
product(id: $id) {
price
}
}
`;
export function PriceBadge({ id }: { id: string }) {
const { data, loading } = useQuery(PRODUCT_PRICE, { variables: { id } });
if (loading) return <span>...</span>;
return <span>${data.product.price}</span>;
}
If the price changes server-side, the Server Component's page.tsx reflects it on the next request after its 60-second revalidation window; Apollo's cache in the browser reflects it only when that specific query is re-run or its cache entry is explicitly invalidated — two independent staleness windows for the exact same underlying number.
GET request, which Next.js caches by default in most fetch configurations, a POST request needs an explicit next: { revalidate } or cache option — otherwise every Server Component fetch to a GraphQL endpoint hits the network fresh, cache or no cache.revalidatePath or revalidateTag has zero effect on a Client Component's Apollo cache holding the same data — they're separate systems that happen to both be called "cache."useQuery hook — neither does anything useful in a component that only ever runs once on the server and never re-renders in the browser. Server Components should just use fetch directly.Nothing about GraphQL changes how Next.js's request memoization and data cache work under the hood — a GraphQL call is exactly the fetch() call this track's caching lesson describes, with a POST method and a JSON body that happens to contain a query string instead of hitting a REST-shaped URL. The genuinely new idea here is the client-side normalized cache a GraphQL library adds on top, which has no equivalent anywhere else in this track because plain REST fetching in a Client Component typically doesn't come with one built in.