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.

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.
| Route | Code | Result on two calls |
|---|---|---|
| GET status/[id] | Plain handler, fetch to the status route | checkedAt changed each call |
| GET me | Plain handler, no dynamic input | Built as dynamic, fresh each call |
| GET cached/[id] | Helper with 'use cache' and cacheLife(minutes) | Same checkedAt on both calls |
| POST webhook | Raw body, HMAC check | 204 when signed, 401 when not |
| Next.js 16.4 default | Cache Components on in create-next-app | Next.js blog |
Steps
- Keep
GET /v1/jobs/{id}/statusand/resultreads in plain route handlers or in code that runs at request time. Return theterminalandnext_poll_after_secondsvalues 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 whenSUME_COM_WEBHOOK_SIGNING_SECRETis 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
- Nine 20 s voice files, one 180 s spine: Timeline audio.parts
Join nine 20-second voice files into a 180 s Short spine with Timeline 1.0 audio.parts (up to 20 slices, gapless, no re-TTS) for $0.30 inside one render.
- No progress percent on the Sume video poll: status plus elapsed time
The poll has status, not a percentage. How to build an honest progress label from pending, in_progress and elapsed seconds, with a 30-second interval.
- Node 26.11 isValidHeaderValue: check a Sume Idempotency-Key first
Node 26.11 adds http.isValidHeaderValue. Use it to reject a bad Idempotency-Key before POST /v1/images, with a fallback for older Node, and see its limits.
- Node 26.11 ships Undici 8.11.2: tell a Sume poll timeout from an abort
Catch TimeoutError separately from AbortError when a Sume status poll in Node fetch times out, and know which Sume calls are safe to repeat after that timeout.
Written by Sume