Next.js 16.4 Cache Components: keep Sume job polls out of use cache

Next.js 16.4 enables Cache Components in new apps. We built a Sume status route both ways: a plain GET handler stayed live, a use cache helper froze one answer.

4 min readSume
All posts

Do not put a Sume job status read inside a 'use cache' function. Next.js 16.4, published October 6, makes create-next-app enable Cache Components by default, and the post describes 'use cache' as a component-level version of the Cache-Control header. A status answer of "processing" that gets cached is a status that never changes until the cache entry expires. In our test the plain route handler returned a new checkedAt on every request, while a 'use cache' helper returned the same value twice.

The fix is boring: leave job polls and webhook handlers uncached. In our test, route handlers in a Cache Components app were not cached unless we opted in, so the safe code is the code you already had. The risk is a refactor that moves the fetch into a shared helper and a well-meaning 'use cache' on top of it.

What we ran

We built a small app with Next.js 16.4.0, cacheComponents: true and React 19.3, with four route handlers, then ran next start and called each twice a second apart against a local stand-in for the Sume API. The build listed all four routes as dynamic. The table lists what we saw, and the Sume field names come from the jobs and results docs.

Next.js 16.4 Cache Components test with Sume routes (read 2026-10-08)
RouteCodeResult on two calls
GET status/[id]Plain handler, fetch to the status routecheckedAt changed each call
GET mePlain handler, no dynamic inputBuilt as dynamic, fresh each call
GET cached/[id]Helper with 'use cache' and cacheLife(minutes)Same checkedAt on both calls
POST webhookRaw body, HMAC check204 when signed, 401 when not
Next.js 16.4 defaultCache Components on in create-next-appNext.js blog

Steps

  • Keep GET /v1/jobs/{id}/status and /result reads in plain route handlers or in code that runs at request time. Return the terminal and next_poll_after_seconds values to the client, which decides when to ask again.
  • If you want caching, cache things that do not change after a job is done: a finished artifact URL record, or the model catalog, never the status of a live job.
  • In the webhook route, read request.text() once and verify the HMAC before parsing. Refuse to run when SUME_COM_WEBHOOK_SIGNING_SECRET is empty.
  • Add a test that calls your status route twice with a stand-in that changes its answer, and fails if the second response is stale.

What to avoid

This helper looks tidy and breaks polling. Both calls to GET inside the cache lifetime return the first status.

import { cacheLife } from "next/cache";

async function readStatus(id) {
  "use cache"; // the returned value is stored and reused
  cacheLife("minutes");
  const res = await fetch(`https://api.sume.com/v1/jobs/${id}/status`, {
    headers: { Authorization: `Bearer ${process.env.SUME_API_KEY}` },
  });
  return res.json(); // a "processing" answer can be served again
}
export async function GET(_request, { params }) {
  return Response.json(await readStatus((await params).id));
}

What to ship

This handler has no cache directive, and it returns only the fields a client needs.

// app/api/sume/status/[id]/route.js - no "use cache" anywhere on this path
export async function GET(_request, { params }) {
  const { id } = await params;
  const res = await fetch(`https://api.sume.com/v1/jobs/${encodeURIComponent(id)}/status`, {
    headers: { Authorization: `Bearer ${process.env.SUME_API_KEY}` },
  });
  const body = await res.json();
  return Response.json({ upstream: res.status, terminal: body.data?.terminal ?? false });
}

What Sume does not do

Sume does not control your caching layer, so the freshness of a poll is up to your app. Sume's own guidance is to keep polling the status route as a backup next to webhooks, which only helps if that route returns live data.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume