Unexpected API Route Caching in Next.js on Vercel

Description

I’m experiencing an issue with my Next.js (app Router) API routes deployed on Vercel. All API routes are being unexpectedly cached, despite multiple attempts to prevent caching. This is causing stale data to be served, even when the underlying data source (Strapi CMS) has been updated.

Steps to Reproduce

  1. Deploy a Next.js application with API routes to Vercel
  2. Set up API routes to fetch data from Strapi CMS
  3. Make changes to the data in Strapi CMS
  4. Fetch data from the API routes using a tool like Insomnia

Expected Behavior

The API routes should return the most up-to-date data from Strapi CMS.

Actual Behavior

The API routes are returning stale data that was present at build time. Changes made in Strapi CMS are not reflected in the API responses.

Code Example

Here’s an example of one of the affected API routes:

javascript

Copy

import { NextResponse } from "next/server";
import { gql } from "@apollo/client";
import { client } from "@/libs/apollo-client";

const GET_TWEETS = gql`
  query GetTweets {
    twitters(sort: "publishedAt:desc", pagination: { pageSize: 15 }) {
      data {
        id
        attributes {
          contenido
          publishedAt
        }
      }
    }
  }
`;

export async function GET() {
  try {
    const { data } = await client.query({
      query: GET_TWEETS,
      fetchPolicy: "network-only",
    });

    const tweets = data.twitters.data.map((tweet) => ({
      id: tweet.id,
      contenido: tweet.attributes.contenido,
      publishedAt: tweet.attributes.publishedAt,
    }));
    console.log(
      data.twitters.data[0].attributes.contenido,
      tweets[0].contenido
    );
    return new Response(JSON.stringify(tweets), {
      status: 200,
      headers: {
        "Cache-Control": "max-age=10",
        "CDN-Cache-Control": "max-age=60",
        "Vercel-CDN-Cache-Control": "max-age=3600",
      },
    });
  } catch (error) {
    console.error("Error:", error);
    const errorResponse = NextResponse.json(
      { error: "Error al obtener los tweets" },
      { status: 500 }
    );
    errorResponse.headers.set("Cache-Control", "no-store, max-age=0");
    return errorResponse;
  }
}

export const dynamic = "force-dynamic";

Attempted Solutions

  1. Using export const dynamic = "force-dynamic";
  2. Setting various Cache-Control headers
  3. Using fetchPolicy: "network-only" in Apollo Client
  4. Checking responses with Insomnia to confirm the issue

None of these solutions have resolved the caching problem.

This issue is affecting all API routes in the application, not just the example provided. It’s critical for our application to always serve the most recent data from Strapi CMS.

Any assistance in resolving this unexpected caching behavior would be greatly appreciated.

I’m running into a similar issue, with a very similar set up using Payload instead of Strapi.
We are running Next.js 15.0.0-canary.173.

I’ve attached an image of the response headers. You can see X-Vercel-Cache is HIT, and the Age header is stale despite the max-age=0 Cache-Control header (I’m also not sure where this header value is coming from or how to adjust it.)

Screenshot 2024-10-11 at 12.11.00 PM

I think I finally got it working:

On my client component:

  useEffect(() => {
    const fetchSubscriptions = async () => {
      try {
        const response = await fetch('/api/subscriptions', {
          cache: 'no-store',
        });
        if (!response.ok) throw new Error('Failed to fetch subscriptions');

        const data = await response.json();
        setSubscriptions(data);
      } catch (err) {
        setError('Failed to load subscriptions. Please try again.');
      } finally {
        setLoading(false);
      }
    };

note the: cache: ‘no-store’,

But you also have to add revalidate in our API route:

import { NextResponse } from 'next/server';
import Stripe from 'stripe';

export const revalidate = 0; // Revalidate on every request

export async function GET() {
<...>
    //not sure if needed but it works so I am not touching it
    return NextResponse.json(subscriptions, {
      headers: {
        'Content-Type': 'application/json',
        'Cache-Control': 'no-store, max-age=0, must-revalidate', // Prevent caching
      },

This was a 2 day journey of frustration. I hope this helps.

Thank you for coming back with your solution, @svtdevops! That is SO helpful. :pray:

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.