cloudflare-next-intl 0.8.60 → 0.8.61

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -593,7 +593,11 @@ third party without consent can itself be GDPR-relevant.
593
593
 
594
594
  #### Stale Deploy & Chunk Load Error Recovery
595
595
 
596
- When a new version of your application is deployed to Cloudflare Workers, users on older client sessions may encounter `ChunkLoadError` or failed dynamic imports when requesting outdated chunks. Use `isStaleDeployError` and `clearClientCache` in error boundaries or global error handlers to automatically recover:
596
+ When a new version of your application is deployed to Cloudflare Workers, users on older client sessions may encounter `ChunkLoadError` or failed dynamic imports when requesting outdated chunks.
597
+
598
+ `IntlHelperScript` renders an early-catch `<script>` (production only, id `stale-deploy-early-catch`) that runs before hydration and listens for `window.error`/`unhandledrejection` events matching the same patterns as `isStaleDeployError` (inlined as JSON, so it stays in sync with `staleDeployPatterns` config), then force-reloads once per build id. This covers the case a React-level recovery (`useStaleDeployRecovery` below) cannot: when the chunk that failed to load is part of your own error boundary/global-error bundle, React never gets a chance to render the recovery UI. Both layers share the same `sessionStorage['stale-deploy-recovery-reloaded']` marker keyed by build id, so they can't double-reload each other. No setup beyond rendering `<IntlHelperScript />` is required.
599
+
600
+ For errors that don't crash the module graph itself (a normal thrown error reaching an error boundary), use `isStaleDeployError` and `clearClientCache` in error boundaries or global error handlers to automatically recover:
597
601
 
598
602
  ```typescript
599
603
  import { isStaleDeployError, clearClientCache } from "cloudflare-next-intl/errorHandling";
@@ -6,6 +6,10 @@
6
6
  * - redirects to the locale-prefixed URL if the locale cookie disagrees
7
7
  * with the current path (covers client-side navigation edge cases)
8
8
  * - (prod only) checks `BUILD_ID` and force-reloads on stale deploys
9
+ * - (prod only) listens for `error`/`unhandledrejection` events matching
10
+ * `isStaleDeployError`'s patterns and force-reloads once per build id —
11
+ * catches a stale-chunk failure even when the failing chunk is your own
12
+ * error boundary, before React (and `useStaleDeployRecovery`) ever mounts
9
13
  * - loads `recaptcha/api.js?render=explicit` when `firebaseAuth.appCheck`
10
14
  * has a `recaptchaV3SiteKey` and `useExplicitRecaptchaScript` isn't
11
15
  * `false`, so `window.grecaptcha` is ready before App Check's
@@ -2,6 +2,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
2
2
  import { isDarkCookieKey, localeCookieName } from "../../config/cookie_key";
3
3
  import config from "../../config/intl_config";
4
4
  import ClientHelperScript from "../../client/components/client_helper_script";
5
+ import { defaultStaleDeployPatterns } from "../../error_handling/is_stale_deploy_error";
5
6
  const isDev = process.env.NODE_ENV === 'development';
6
7
  const appCheck = config.firebaseAuth?.appCheck;
7
8
  const shouldLoadExplicitRecaptchaScript = !!appCheck?.recaptchaV3SiteKey && appCheck.useExplicitRecaptchaScript !== false;
@@ -14,6 +15,10 @@ const secureCookieAttribute = isDev ? '+ " Secure;"' : '';
14
15
  * - redirects to the locale-prefixed URL if the locale cookie disagrees
15
16
  * with the current path (covers client-side navigation edge cases)
16
17
  * - (prod only) checks `BUILD_ID` and force-reloads on stale deploys
18
+ * - (prod only) listens for `error`/`unhandledrejection` events matching
19
+ * `isStaleDeployError`'s patterns and force-reloads once per build id —
20
+ * catches a stale-chunk failure even when the failing chunk is your own
21
+ * error boundary, before React (and `useStaleDeployRecovery`) ever mounts
17
22
  * - loads `recaptcha/api.js?render=explicit` when `firebaseAuth.appCheck`
18
23
  * has a `recaptchaV3SiteKey` and `useExplicitRecaptchaScript` isn't
19
24
  * `false`, so `window.grecaptcha` is ready before App Check's
@@ -32,6 +37,47 @@ const secureCookieAttribute = isDev ? '+ " Secure;"' : '';
32
37
  export default function HelperScript() {
33
38
  return _jsxs(_Fragment, { children: [shouldLoadExplicitRecaptchaScript &&
34
39
  _jsx("script", { src: "https://www.google.com/recaptcha/api.js?render=explicit", async: true, defer: true }), !isDev &&
40
+ _jsx("script", { id: "stale-deploy-early-catch", dangerouslySetInnerHTML: {
41
+ __html: `(function() {
42
+ var patterns = ${JSON.stringify(defaultStaleDeployPatterns)};
43
+ var key = 'stale-deploy-recovery-reloaded';
44
+ var attemptedThisLoad = false;
45
+ function isStale(msg) {
46
+ if (msg === undefined || msg === null) return true;
47
+ msg = String(msg).toLowerCase();
48
+ for (var i = 0; i < patterns.length; i++) {
49
+ if (msg.indexOf(patterns[i]) > -1) return true;
50
+ }
51
+ return false;
52
+ }
53
+ function recover(msg) {
54
+ // One reload attempt per page load, no matter how many matching
55
+ // errors fire in a row (a single stale deploy commonly throws
56
+ // several near-simultaneous chunk failures) — without this guard
57
+ // each one would race to read-then-write the same sessionStorage
58
+ // key and could re-trigger reload() multiple times before the
59
+ // first navigation lands.
60
+ if (attemptedThisLoad) return;
61
+ try {
62
+ if (!isStale(msg)) return;
63
+ var buildId = localStorage.getItem('buildId') || 'unknown';
64
+ // Same marker/key as useStaleDeployRecovery: one reload per
65
+ // build id, so a repeat failure on a build that already spent
66
+ // its reload falls through instead of reloading forever.
67
+ if (sessionStorage.getItem(key) === buildId) return;
68
+ attemptedThisLoad = true;
69
+ sessionStorage.setItem(key, buildId);
70
+ window.location.reload();
71
+ } catch (e) {
72
+ console.error('Stale Deploy Early Catch Script Error:', e);
73
+ }
74
+ }
75
+ window.addEventListener('error', function(e) { recover(e.message); });
76
+ window.addEventListener('unhandledrejection', function(e) {
77
+ recover(e.reason && e.reason.message);
78
+ });
79
+ })();`
80
+ } }), !isDev &&
35
81
  _jsx("script", { id: "build-id-script", children: `(async function() {
36
82
  try {
37
83
  const resp = await fetch('/BUILD_ID', { method: 'HEAD', cache: 'no-store' });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudflare-next-intl",
3
- "version": "0.8.60",
3
+ "version": "0.8.61",
4
4
  "description": "Optimized Next Intl Package Special for App Router and Cloudflare",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -209,6 +209,10 @@
209
209
  "types": "./dist/src/error_handling/clear_client_cache.d.ts",
210
210
  "import": "./dist/src/error_handling/clear_client_cache.js"
211
211
  },
212
+ "./useStaleDeployRecovery": {
213
+ "types": "./dist/src/error_handling/use_stale_deploy_recovery.d.ts",
214
+ "import": "./dist/src/error_handling/use_stale_deploy_recovery.js"
215
+ },
212
216
  "./db": {
213
217
  "types": "./dist/src/db/index.d.ts",
214
218
  "import": "./dist/src/db/index.js"