feat: retry on 503, add Retry-After date (RFC1123) support
Description
This PR introduces a with503Retry higher-order function that wraps fetch and provides robust, spec-aligned retry handling for HTTP 503 Service Unavailable responses. It retries only on 503s, passes all other responses through untouched, and fully honors any provided AbortSignal.
Key Features
- 503-only retry behavior
- Non-503 responses return immediately.
- Up to
MAX_RETRIES = 5retry attempts for 503 responses.
- Standards-compliant and defnesive handling of Retry-After:
- Supports both delta-seconds and HTTP-date formats.
- Returns a millisecond delay (
getRetryAfterMs) or seconds (getRetryAfterSeconds) with clear unit types in name. - Past dates resolve to 0 (retry immediately).
- Invalid headers are ignored (treated as if missing).
- Upper bound clamped to
MAX_DELAY_MS = 60_000to avoid pathological server values.
- Exponential backoff with jitter
- If no valid Retry-After header is present:
- Delay is randomized between 0 and 2^attempt * BASE_DELAY_MS.
-
BASE_DELAY_MS = 500 - Helps reduce retry storms and load cascades.
- Abort-safe retry waits
- All waits use waitForTimeoutOrCancel, which:
- Resolves on timeout.
- Rejects immediately with a DOMException "AbortError" if an AbortSignal fires.
- Ensures retry loops terminate fast on caller cancellation.
- No change to global fetch
- The wrapper returns a new function and does not mutate globals, making it safe for tests and modular usage.
Motivations
- 503 responses represent transient server overload conditions; retrying is generally safe and recommended.
-
Retry-Aftersupport ensures compliance with server instructions and avoids piling onto a recovering subsystem. - Exponential backoff + jitter reduces thundering herds.
- Abort propagation ensures requests do not hang during application shutdown, navigation, or controller-driven cancellation.
Implementation Notes
- The retry loop is explicit and easy to audit.
-
getRetryAfterMsis strict, predictable, and does not attempt to interpret exotic date formats beyond what Date can parse. - The wrapper’s return type remains a normal fetch Promise<Response>, so no API changes for consumers.
Next Steps / Possible Future Improvements
- test cases to validate integration with client
- make wrapper more generic
Checklist
- [x]
pnpm testruns as expected. - [x]
pnpm buildruns as expected. - [ ] (If applicable) JSDoc comments have been added or updated for any package exports
- [ ] (If applicable) Documentation has been updated
Type of change
- [ ] 🐛 Bug fix
- [x] 🌟 New feature
- [ ] 🔨 Breaking change
- [ ] 📖 Refactoring / dependency upgrade / documentation
- [ ] other:
⚠️ No Changeset found
Latest commit: 9fba31bee7509bc612bf9ea41c464f9b007d3fbb
Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.
This PR includes no changesets
When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types
Click here to learn what changesets are, and how to add one.
Click here if you're a maintainer who wants to add a changeset to this PR
[!IMPORTANT]
Review skipped
Draft detected.
Please check the settings in the CodeRabbit UI or the
.coderabbit.yamlfile in this repository. To trigger a single review, invoke the@coderabbitai reviewcommand.You can disable this status message by setting the
reviews.review_statustofalsein the CodeRabbit configuration file.
✨ Finishing touches
🧪 Generate unit tests (beta)
- [ ] Create PR with unit tests
- [ ] Post copyable unit tests in a comment
- [ ] Commit unit tests in branch
henry/add-retry-after-date-support
Comment @coderabbitai help to get the list of available commands and usage tips.
The latest updates on your projects. Learn more about Vercel for GitHub.
| Project | Deployment | Preview | Comments | Updated (UTC) |
|---|---|---|---|---|
| clerk-js-sandbox | Preview | Comment | Dec 5, 2025 11:09pm |