cloudflare-next-intl 0.8.44 → 0.8.46

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
@@ -532,6 +532,34 @@ export default function GlobalError({
532
532
  - `getStaleDeployPatterns(): readonly string[]`: Returns the currently active pattern list.
533
533
  - `clearClientCache(): Promise<void>`: Best-effort cleanup that deletes all CacheStorage caches (`window.caches`), unregisters active Service Workers, and clears `sessionStorage`.
534
534
 
535
+ For a ready-made recovery flow (recommended over wiring `isStaleDeployError` + `clearClientCache` by hand), use the `useStaleDeployRecovery` hook:
536
+
537
+ ```typescript
538
+ 'use client';
539
+
540
+ import { useStaleDeployRecovery } from "cloudflare-next-intl/errorHandling";
541
+
542
+ export default function GlobalError({
543
+ error,
544
+ reset,
545
+ }: {
546
+ error: Error & { digest?: string };
547
+ reset: () => void;
548
+ }) {
549
+ // Optionally pass a server action to clear server-side cookies/cache
550
+ // before the reload; runs alongside clearClientCache() and any
551
+ // rejection is ignored (best-effort).
552
+ const isRecovering = useStaleDeployRecovery(error /*, clearServerCookies */);
553
+
554
+ if (isRecovering) return <LoadingIndicator />;
555
+
556
+ // ... render your normal error UI
557
+ }
558
+ ```
559
+
560
+ - `useStaleDeployRecovery(error: Error, onRecover?: () => Promise<unknown>, delayMs = 5000): boolean`: Detects a stale-deploy error via `isStaleDeployError` and, **once per build id**, waits `delayMs`, runs `onRecover` (if provided) and `clearClientCache` in parallel, then reloads the page. The build id is read from `localStorage['buildId']` (set by `IntlHelperScript`'s `BUILD_ID` check) and a `sessionStorage` marker records which build id already spent its reload, so a repeat failure on the same build falls through to `false` (render your normal error UI) instead of reloading forever. A redeploy changes the build id and re-arms exactly one more attempt.
561
+ - `shouldRecoverFromStaleDeploy(error: Error, buildId: string, marker: string | null): boolean`: The pure predicate behind the hook, exported for testing.
562
+
535
563
  ### Database (`db`)
536
564
 
537
565
  Thin Postgres/Drizzle data-access layer over a Postgres connection string
@@ -34,6 +34,8 @@ export const defaultIgnoredConsoleErrors = [
34
34
  "FirebaseServerApp authIdToken is invalid: the token has expired.",
35
35
  "failed Error: Database is closing/hidden",
36
36
  'Failed to fetch RSC payload',
37
+ 'The above error occurred in a React component',
38
+ 'The connection to the page was unexpectedly closed',
37
39
  ...(process.env.NODE_ENV === 'development'
38
40
  ? ['A DurableObjectNamespace in the config referenced the class "DOQueueHandler", but no such Durable Object class is exported from the worker. Please make sure the class name matches,']
39
41
  : []),
@@ -9,4 +9,5 @@ export { default as formatErrorMessage } from './format_error_message';
9
9
  export { defaultIgnoredConsoleErrors } from './default_ignored_console_errors';
10
10
  export { default as isStaleDeployError, defaultStaleDeployPatterns, setStaleDeployPatterns, getStaleDeployPatterns, } from './is_stale_deploy_error';
11
11
  export { default as clearClientCache } from './clear_client_cache';
12
+ export { default as useStaleDeployRecovery, shouldRecoverFromStaleDeploy } from './use_stale_deploy_recovery';
12
13
  export type { ErrorHandlingParams, ErrorHandlingRoutingConfig } from '../types/types';
@@ -7,3 +7,4 @@ export { default as formatErrorMessage } from './format_error_message';
7
7
  export { defaultIgnoredConsoleErrors } from './default_ignored_console_errors';
8
8
  export { default as isStaleDeployError, defaultStaleDeployPatterns, setStaleDeployPatterns, getStaleDeployPatterns, } from './is_stale_deploy_error';
9
9
  export { default as clearClientCache } from './clear_client_cache';
10
+ export { default as useStaleDeployRecovery, shouldRecoverFromStaleDeploy } from './use_stale_deploy_recovery';
@@ -5,6 +5,8 @@ export const defaultStaleDeployPatterns = [
5
5
  'connection closed',
6
6
  'rsc payload',
7
7
  'minified react error #412',
8
+ 'the above error occurred in a react component',
9
+ 'the connection to the page was unexpectedly closed',
8
10
  ];
9
11
  let activePatterns = defaultStaleDeployPatterns;
10
12
  let activeLowercasedPatterns = defaultStaleDeployPatterns.map((p) => p.toLowerCase());
@@ -0,0 +1,10 @@
1
+ export declare function shouldRecoverFromStaleDeploy(error: Error, buildId: string, marker: string | null): boolean;
2
+ /**
3
+ * Detects a stale-deploy error and, once per build id, silently clears client
4
+ * caches and reloads after `delayMs`. Returns whether a reload is pending so
5
+ * the caller can render a loading state instead of the error UI while it
6
+ * waits. `onRecover` runs before the reload (e.g. to clear server cookies via
7
+ * a server action) and its rejection is ignored — cache clearing is
8
+ * best-effort.
9
+ */
10
+ export default function useStaleDeployRecovery(error: Error, onRecover?: () => Promise<unknown>, delayMs?: number): boolean;
@@ -0,0 +1,60 @@
1
+ 'use client';
2
+ import { useEffect, useState } from 'react';
3
+ import isStaleDeployError from './is_stale_deploy_error';
4
+ import clearClientCache from './clear_client_cache';
5
+ const RECOVERY_RELOAD_KEY = 'stale-deploy-recovery-reloaded';
6
+ const BUILD_ID_KEY = 'buildId';
7
+ function currentBuildId() {
8
+ try {
9
+ return localStorage.getItem(BUILD_ID_KEY) ?? 'unknown';
10
+ }
11
+ catch {
12
+ return 'unknown';
13
+ }
14
+ }
15
+ // One silent reload per deployment. The marker carries the build id the reload
16
+ // was spent on, so a redeploy re-arms exactly one more attempt while a repeat
17
+ // failure on the same build falls through to the caller's error UI instead of
18
+ // spinning forever.
19
+ export function shouldRecoverFromStaleDeploy(error, buildId, marker) {
20
+ return isStaleDeployError(error) && marker !== buildId;
21
+ }
22
+ function canRecover(error) {
23
+ if (typeof window === 'undefined')
24
+ return false;
25
+ try {
26
+ return shouldRecoverFromStaleDeploy(error, currentBuildId(), sessionStorage.getItem(RECOVERY_RELOAD_KEY));
27
+ }
28
+ catch {
29
+ return false;
30
+ }
31
+ }
32
+ /**
33
+ * Detects a stale-deploy error and, once per build id, silently clears client
34
+ * caches and reloads after `delayMs`. Returns whether a reload is pending so
35
+ * the caller can render a loading state instead of the error UI while it
36
+ * waits. `onRecover` runs before the reload (e.g. to clear server cookies via
37
+ * a server action) and its rejection is ignored — cache clearing is
38
+ * best-effort.
39
+ */
40
+ export default function useStaleDeployRecovery(error, onRecover, delayMs = 5000) {
41
+ const [recovering] = useState(() => canRecover(error));
42
+ useEffect(() => {
43
+ if (!recovering)
44
+ return;
45
+ const buildId = currentBuildId();
46
+ const timeout = setTimeout(() => {
47
+ Promise.all([onRecover?.().catch(() => undefined), clearClientCache().catch(() => undefined)])
48
+ .finally(() => {
49
+ try {
50
+ sessionStorage.setItem(RECOVERY_RELOAD_KEY, buildId);
51
+ }
52
+ catch { /* storage unavailable */ }
53
+ window.location.reload();
54
+ });
55
+ }, delayMs);
56
+ return () => clearTimeout(timeout);
57
+ // eslint-disable-next-line react-hooks/exhaustive-deps
58
+ }, [recovering]);
59
+ return recovering;
60
+ }
package/llms.txt CHANGED
@@ -28,9 +28,10 @@ other subpath can be used.
28
28
  - `./dbEslint` — flat-config ESLint fragment banning direct `@supabase/supabase-js`, `pg`, `postgres`, and deep `dist/` imports in application code.
29
29
  - `./dbHelpers` — generic Drizzle SQL helper functions (`excluded`, `onConflictSet`, `ago`, `currentDate`, `windowCount`, `unnestLateral`, `ascNullsLast`, `alwaysTrue`, `lateral`, `aliasColumn`, `minOf`, `maxOf`, `roundReal`, `multiply`, `scalarFromCte`) for use with `./db`.
30
30
  - `./vite` — `buildIdAsset(fileName?)`: Vite plugin to emit the client `BUILD_ID` asset (from `__VINEXT_SHARED_BUILD_ID` or `__VINEXT_BUILD_ID`) during build in Vinext/Vite environments.
31
- - `./errorHandling` — error reporting & stale deploy recovery barrel: `reportError`, `withErrorHandling`, `installConsoleErrorOverride`, `installGlobalErrorOverride`, `stringifyUnknown`, `formatErrorMessage`, `defaultIgnoredConsoleErrors`, `createServerErrorAction`, `isStaleDeployError`, `defaultStaleDeployPatterns`, `setStaleDeployPatterns`, `getStaleDeployPatterns`, `clearClientCache`.
31
+ - `./errorHandling` — error reporting & stale deploy recovery barrel: `reportError`, `withErrorHandling`, `installConsoleErrorOverride`, `installGlobalErrorOverride`, `stringifyUnknown`, `formatErrorMessage`, `defaultIgnoredConsoleErrors`, `createServerErrorAction`, `isStaleDeployError`, `defaultStaleDeployPatterns`, `setStaleDeployPatterns`, `getStaleDeployPatterns`, `clearClientCache`, `useStaleDeployRecovery`, `shouldRecoverFromStaleDeploy`.
32
32
  - `./isStaleDeployError` — `isStaleDeployError(error, patterns?)`, `setStaleDeployPatterns(patterns)`, `getStaleDeployPatterns()`: detector returning `true` for version skew / chunk load / hydration errors (ChunkLoadError, failed to fetch, loading CSS chunk, connection closed, RSC payload failure, minified error #412) with fast pre-lowercased pattern cache and intl-config integration (`errorHandling.staleDeployPatterns`).
33
33
  - `./clearClientCache` — `clearClientCache()`: async helper wiping `window.caches`, unregistering service workers, and clearing `sessionStorage` for recovering from stale deployments.
34
+ - `useStaleDeployRecovery(error, onRecover?, delayMs?)` (client hook, in `./errorHandling`) — once per build id (`localStorage['buildId']`, `sessionStorage` marker), waits `delayMs` (default 5000ms), runs optional `onRecover()` + `clearClientCache()` in parallel, then `window.location.reload()`. Returns whether a reload is pending, so caller renders a loading state instead of error UI. `shouldRecoverFromStaleDeploy(error, buildId, marker)` is the pure predicate.
34
35
  - `./createServerErrorAction` — `createServerErrorAction(action, config)`: wrapper for server actions with standardized error reporting.
35
36
 
36
37
  ## `firebaseAuth*` subpaths (require `firebaseAuth` set on your `RoutingConfig`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudflare-next-intl",
3
- "version": "0.8.44",
3
+ "version": "0.8.46",
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",