Using unstable_cache extensions or "use cache" if you’re on NextJS16, you can leverage revalidateTag to invalidate cache across cached calls. However, I noticed today that these won’t work in a route handler that is not async. And I’m not yet sure why.

This was my original (simplified) route handler:

export async function POST(req: Request) {
  try {
    void someTask();
    return NextResponse.json({ done: true });
  } catch (err) {
    return new NextResponse(`Internal error`, {
      status: 500,
    });
  }
}

Note that someTask returns a Promise which I didn’t await initially: the caller only needed to know that someTask was called, not that it succeeded. However, it became evident someTask didn’t behave properly.

someTask gets a list of subscriptions and updates them progressively. After each update, it re-validates cache entries associated with the Subscription tag:

const cachedFindOutdatedSubscriptions = unstable_cache(
  findOutdatedSubscriptions,
  undefined, // Unused (For additional keys)
  {
    tags: ["Subscription"],
  },
);

export default async function someTask() {
  const subscriptions = await cachedFindOutdatedSubscriptions();

  for (const subscription of subscriptions) {
    await updateSubscription(subscription);
    revalidateTag('Subscription');
  }
}

The function cachedFindOutdatedSubscriptions returns subscriptions from a cache entry also tagged with Subscription. You would therefore expect that across repeated calls to this route, the subscriptions array becomes empty when no new subscriptions are created.

This is not what happened; the same subscriptions got reprocessed every single time, served from the cache. As if revalidateTag was not called.

Interestingly, the fix is simply to await the call to someTask in the route handler:

  export async function POST(req: Request) {
    try {
-     void someTask();
+     await someTask();
      return NextResponse.json({ done: true });
    } catch (err) {
      return new NextResponse(`Internal error`, {
        status: 500,
      });
    }
  }

After reading the code for unstable_cache, I got the intuition for the fix. I’m not sure of what is exactly happening under the hood yet, but I think it has to do with the lifetime of the request’s context. I’ll return to this once I understand it properly.