Idempotent Next.js route handlers that survive two instances and a crash #96995
Replies: 2 comments 2 replies
|
This is a useful abstraction, especially because idempotency is easy to get subtly wrong when implemented separately in every Route Handler. The important production consideration is that For production, the backend needs to provide an atomic “claim-or-replay” operation at the storage layer. Redis, Postgres, DynamoDB, etc. can work, but the critical part is that the check-and-create operation must be atomic rather than: if (!exists(key)) {
create(key);
executeHandler();
}Otherwise concurrent duplicate requests can both pass the check before either one records the key. I’d also consider documenting a few semantics explicitly:
One other important distinction: idempotency protects against duplicate execution, but it doesn't automatically make the underlying The wrapper API is nice, though. Keeping it compatible with the standard A production-oriented example would therefore look conceptually like: export const POST = withIdempotency(handler, {
backend: redisBackend,
ttl: 24 * 60 * 60,
});The strongest part of this approach is that application code doesn't have to repeatedly implement the same retry/concurrency logic. If |
|
This looks incredibly useful, @violetta98! Handling idempotency in Serverless/Edge environments like Next.js route handlers is usually a headache, especially with client retries and race conditions between instances. Relying on atomic operations per backend (like the Lua script for Redis or Quick question regarding the storage: how does Great work on keeping it zero-dependency too. Definitely starred! ⭐ |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
A dropped connection on the way back from
/api/chargemeans the browser retries, and your handler runs a second time. Route handlers are just Fetch handlers, so this wraps one.A retry carrying the same
Idempotency-Keyreplays the first response and never reaches the handler. The claim is one atomic operation per backend, a Lua script on Redis,INSERT … ON CONFLICTon Postgres, so two instances racing resolve to a single run rather than both getting past a check. If the winner dies mid-charge its lease expires on the storage clock, and a fencing token stops its late write from overwriting the real result.There's an
InMemoryBackendfor local dev. Production is Redis, Postgres, Mongo or DynamoDB.Zero dependencies, Apache-2.0: https://github.com/idemkit/idemkit
If you find it useful, a star would be much appreciated.
All reactions