Next.js 16.4 GET route handler: poll a Sume job at request time
With Cache Components, Next.js 16.4 runs GET handlers at request time. A Sume job status route stays live if it sends no-store and follows the poll hint.

A GET route that proxies a Sume job status stays live in Next.js 16.4 as long as it reads something per request, such as a query parameter or a header. The Route Handlers page (version 16.4.0) says that with Cache Components, GET handlers run at request time by default and prerendering stops when the handler touches request.body or headers(). Your browser can then poll the route and see fresh status.
This post shows a status route that forwards the poll, passes through the wait hint Sume returns, and avoids the one mistake that serves a stale processing state.
What the 16.4 docs say about GET
Before Cache Components, a GET handler with no dynamic input could be prerendered under force-static. The docs still list that opt-in, and they say other methods are never cached. In 16.4 the recommended mode runs GET at request time unless you opt in.
Do not add use cache inside the handler body; the docs say it cannot sit directly there. If you cache a helper, never cache the Sume status call.
| Method | Cached by default | How to opt in |
|---|---|---|
| GET | No | export const dynamic = 'force-static' |
| POST | Never | Not possible |
| GET with Cache Components | Runs at request time | use cache in a helper, not the handler body |
GET that reads headers() | Prerender stops | Nothing to do |
What the Sume status payload gives you
A Sume job envelope returns status (queued, processing, completed, failed, canceled), terminal, result_ready and next_poll_after_seconds. Pass the wait hint to the browser instead of picking your own interval.
Poll only until terminal is true. A timeout on your side does not cancel the job, and you should not submit again; a retry of the submit uses the same Idempotency-Key.
A status route
The route reads the job id from the URL and forwards one request. The Sume client sends the key in x-api-key; do not add an Authorization header as well.
export async function GET(request: Request) {
const id = new URL(request.url).searchParams.get("job");
const key = process.env.SUME_API_KEY;
if (!id || !key) return Response.json({ error: "bad request" }, { status: 400 });
const res = await fetch(`https://api.sume.com/v1/jobs/${encodeURIComponent(id)}`, {
headers: { "x-api-key": key },
cache: "no-store",
});
const body = await res.json();
return Response.json(body, {
status: res.status,
headers: { "cache-control": "no-store" },
});
}Limits to plan for
Status reads can hit 429 rate_limited; treat that as poll backpressure and read retry-after. For long jobs, prefer a webhook and use this route only as a fallback for the page that shows progress.
- Read
next_poll_after_secondsand return it to the client. - Send
cache-control: no-storeso a CDN never stores aprocessingresponse. - Never expose the API key to browser code.
Choose the polling client
A browser that polls your route should wait for the value your route returns, not a fixed 2 seconds. Return next_poll_after_seconds in your own response and have the client schedule its next call from it. If the job is queued, Sume may ask for a longer interval than when it is processing, and a long queue is exactly when a fixed fast loop hurts.
Stop on terminal. Then, if result_ready is true, make a second request for the result so that your status route stays small. Media URLs on media.sume.com are durable, so the result page can be cached after the job ends without a staleness problem for the file itself.
- Do not poll from many tabs; one poll per job per user is enough.
- Do not retry a failed status read faster than
retry-afterallows. - Show
queuedas waiting, not as an error; a full concurrency limit still accepts the job.
Sources
Related posts
More in Developers
- 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.
- 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.
Written by Sume