Skip to content

perf: defer regional cache writes off the response path - #1345

Open
km-tr wants to merge 2 commits into
opennextjs:mainfrom
km-tr:fix/defer-incremental-cache-write
Open

perf: defer regional cache writes off the response path#1345
km-tr wants to merge 2 commits into
opennextjs:mainfrom
km-tr:fix/defer-incremental-cache-write

Conversation

@km-tr

@km-tr km-tr commented Aug 16, 2026

Copy link
Copy Markdown

Fixes #1343

What

RegionalCache.set now hands both writes to ctx.waitUntil instead of awaiting them.

Why

Next.js awaits incrementalCache.set while producing the response:

Next.js  response-cache/index.js   await incrementalCache.set(...)
  └─ OpenNext adapters/cache.ts     await globalThis.incrementalCache.set(...)
       └─ RegionalCache.set          await this.store.set(...) → r2.put()

So the store round-trip lands in the TTFB. response-cache/index.js does not use the
returned value — it returns the same entry it passed in — so the writes can be deferred
without changing what is served.

The read path already defers its cache updates the same way:

// get(): refresh the regional cache in the background
getCloudflareContext().ctx.waitUntil(this.store.get(key, cacheType).then(...))
// get(): populate the regional cache after a store hit
getCloudflareContext().ctx.waitUntil(this.putToCache({ key, cacheType, entry }))

This makes set consistent with it — putToCache was previously deferred when reached
from get and awaited when reached from set.

Measured impact

Production app on Workers with the R2 incremental cache, using Workers automatic tracing.

  • r2_put spans: median 976 ms, up to 2,048 ms (14 of 20 sampled traces)
  • TTFB on ISR misses before the change: 2.4–4.6 s

We ran this as a local override on a staging deployment, four requests each on ISR misses:

TTFB (median)
deferred 1.97 s
awaited 2.45 s

Repeated requests to the same page still returned x-opennext-cache: HIT at 0.32–0.69 s,
so cache behaviour is unchanged.

Trade-off

Deferring the write widens the window in which a concurrent request for the same page
misses the cache and re-renders. That window already exists for the duration of the write;
deferring shifts its start rather than creating it.

Notes

  • Errors are still caught and logged inside the deferred task, so a failing write does not
    surface as an unhandled rejection.
  • Added regional-cache.spec.ts covering the deferred path: set resolves while the store
    write is still pending, both writes run in the background, and a rejected store write does
    not reject the promise handed to waitUntil. The tests fail against the awaiting version.

Open in Devin Review

Next.js awaits incrementalCache.set while producing the response, so the store
write and the regional cache update were both on the critical path. The return
value is unused, so they can run in ctx.waitUntil - the read path already
defers its cache updates the same way.
@changeset-bot

changeset-bot Bot commented Aug 16, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 56a49dc

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@opennextjs/cloudflare Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 1 additional finding in Devin Review.

Open in Devin Review

Comment thread .changeset/defer-regional-cache-write.md Outdated
AGENTS.md and CONTRIBUTING.md list feature | fix | refactor | docs | chore.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incremental cache writes block the response on Workers

1 participant