next.jscachinggraphqlapollo-client

Apollo Client Caching Issue with fetchPolicy: 'no-cache' in Next.js Server Component


I'm working on a Next.js server component to fetch events from a GraphQL server, but I'm encountering an issue where the cache seems to be used despite setting fetchPolicy: 'no-cache'. Here is my setup:

// lib/apollo-client.ts

import { ApolloClient, InMemoryCache } from "@apollo/client";

const client = new ApolloClient({
  uri: process.env.NEXT_PUBLIC_GRAPHQL_URI || 'http://localhost:3000/api/graphql', // Fallback to localhost
  cache: new InMemoryCache(),
  connectToDevTools: true,
  defaultOptions: {
    query: {
      fetchPolicy: 'no-cache',  // Disable caching for queries
      errorPolicy: 'all',
    },
  }
})

export default client;

// app/events/page.tsx

import client from "@/lib/apollo-client";
import { EventProps } from "@/types";
import { getAllRecords } from "@/utils/airtable/client";
import { gql } from "@apollo/client";
import { Box, Grid, Card, CardContent, Typography, CardActions, Button, Divider } from "@mui/material";
import EventsDataGrid from "../components/EventsDataGrid";

const EVENT_QUERY = gql`
    query EventQuery {
        events {
            id
            name
        }
    }
`;

async function fetchData() {
    const events: EventProps[] = [];
    const newEvents: EventProps[] = [];

    // Fetch events from GraphQL
    try {
        client.cache.reset();
        const { data } = await client.query({
            query: EVENT_QUERY,
            fetchPolicy: 'no-cache', // Ensure cache is not used
        });

        events.push(...data.events);
    } catch (error) {
        console.error('Error fetching events from GraphQL:', error);
    }

    // Fetch new events from Airtable
    try {
        const records = await getAllRecords<EventProps>('events', `OR({id} = '', {id} = BLANK())`);
        records.forEach((newEvent) => {
            const { name } = newEvent.fields;
            newEvents.push({
                name,
                airtableId: newEvent.id
            });
        });
    } catch (error) {
        console.error('Error fetching events from Airtable:', error);
    }

    return { events, newEvents };
}

export default async function EventsPage() {
    const { events, newEvents } = await fetchData();

    return (
        <Box sx={{ flexGrow: 1 }}>
            <Grid container spacing={3}>
                {events.map((event) => (
                    <Grid item lg={12} md={12} xs={12} key={event.id}>
                        <Card sx={{ minWidth: 275 }}>
                            <CardContent>
                                <Typography sx={{ fontSize: 14 }} color="text.secondary" gutterBottom>
                                    {event.name}
                                </Typography>
                            </CardContent>
                            <CardActions>
                                <Button href={`event/${event.id}`} size="small">View</Button>
                            </CardActions>
                        </Card>
                    </Grid>
                ))}
                
                {/* New events */}
                <Grid item lg={12} md={12} xs={12}>
                    <Typography variant="h6">
                        New Events
                    </Typography>
                    <Divider />
                    <EventsDataGrid events={newEvents} />
                </Grid>
            </Grid>
        </Box>
    );
}

Issue: Despite setting fetchPolicy: 'no-cache', the GraphQL queries are still returning cached data. I even tried calling client.cache.reset() before the query, but it didn't resolve the issue.

What am I missing or doing wrong? Are there additional steps I need to take to ensure that Apollo Client does not use cached data?


Solution

  • If two queries happen at the exact same time, the network request gets deduplicated.

    As you are using Apollo Client on it's own, and not the "@apollo/experimental-nextjs-app-support" package (which you really should do, streaming SSR like Next.js does cannot be supported without either new React features or a package geared towards the framework you are using!), you are creating one global instance of client.

    That one instance is shared between all incoming network requests, which means shared between all active users - and you easily get into the situation where the same query happens twice at the same time, gets dedpulicated, and that looks as if the cache is being used.

    While you could disable the deduplication, instead I would strongly suggest you use the "@apollo/experimental-nextjs-app-support" package instead, as that ensures that you have an individual instance of ApolloClient per incoming request and data cannot be mixed between users. You don't even need to disable caching.

    import { HttpLink } from "@apollo/client";
    import {
      registerApolloClient,
      ApolloClient,
      InMemoryCache,
    } from "@apollo/experimental-nextjs-app-support";
    
    export const { getClient, query, PreloadQuery } = registerApolloClient(() => {
      return new ApolloClient({
        cache: new InMemoryCache(),
        link: new HttpLink({
          // this needs to be an absolute url, as relative urls cannot be used in SSR
          uri: "http://example.com/api/graphql",
          // you can disable Next.js fetch result caching here if you want to
          // (this does not work if you are rendering your page with `export const dynamic = "force-static"`)
          fetchOptions: { cache: "no-store" },
        }),
      });
    });
    

    On top of all that, it's very possible that Next.js caches outgoing network requests, so you need to set fetchOptions: { cache: "no-store" }, if you want to prevent Next.js from doing so.