cloudflare-next-intl 0.8.41 → 0.8.43
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 +51 -0
- package/dist/src/config/init_config.js +4 -0
- package/dist/src/error_handling/clear_client_cache.d.ts +1 -0
- package/dist/src/error_handling/clear_client_cache.js +18 -0
- package/dist/src/error_handling/index.d.ts +2 -0
- package/dist/src/error_handling/index.js +2 -0
- package/dist/src/error_handling/is_stale_deploy_error.d.ts +4 -0
- package/dist/src/error_handling/is_stale_deploy_error.js +31 -0
- package/dist/src/types/types.d.ts +7 -0
- package/dist/src/vite/build_id_asset.d.ts +2 -0
- package/dist/src/vite/build_id_asset.js +14 -0
- package/llms.txt +5 -0
- package/package.json +19 -2
package/README.md
CHANGED
|
@@ -217,6 +217,8 @@ export function customHandler(request: NextRequest) {
|
|
|
217
217
|
|
|
218
218
|
`intlMiddleware` automatically forwards `x-cf-country` and `x-cf-timezone` headers from `request.cf` so they are immediately available downstream via `next/headers`.
|
|
219
219
|
|
|
220
|
+
Header names default to `['x-cf-country', 'cf-ipcountry']` and `['x-cf-timezone', 'cf-timezone']`. You can customize them globally in `setIntlConfig({ generate: { countryHeaderNames: [...], timezoneHeaderNames: [...] } })` or per call via `getCountry(input, generate, headerNames)` / `getTimezone(input, fallback, generate, headerNames)`.
|
|
221
|
+
|
|
220
222
|
### Vinext Runtime Configuration
|
|
221
223
|
|
|
222
224
|
When deploying under [Vinext](https://github.com/cloudflare/vinext) with `cloudflare:workers`, you can pass your `env` and execution context (`ctx`) directly in `setIntlConfig`:
|
|
@@ -237,6 +239,22 @@ export default setIntlConfig({
|
|
|
237
239
|
});
|
|
238
240
|
```
|
|
239
241
|
|
|
242
|
+
#### Vite Build ID Asset Plugin (`cloudflare-next-intl/vite`)
|
|
243
|
+
|
|
244
|
+
When building client assets with Vite / Vinext for Cloudflare Workers, use `buildIdAsset` to emit the static `BUILD_ID` asset:
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
// vite.config.ts
|
|
248
|
+
import { defineConfig } from "vite";
|
|
249
|
+
import { buildIdAsset } from "cloudflare-next-intl/vite";
|
|
250
|
+
|
|
251
|
+
export default defineConfig({
|
|
252
|
+
plugins: [
|
|
253
|
+
buildIdAsset(), // reads __VINEXT_SHARED_BUILD_ID or __VINEXT_BUILD_ID
|
|
254
|
+
],
|
|
255
|
+
});
|
|
256
|
+
```
|
|
257
|
+
|
|
240
258
|
```tsx
|
|
241
259
|
// Client Components ("use client")
|
|
242
260
|
import { useLocale } from "cloudflare-next-intl/use";
|
|
@@ -450,6 +468,8 @@ export default setIntlConfig({
|
|
|
450
468
|
// formattedMessage is a ready-to-print "[classOrMethodName] Error: ..." string
|
|
451
469
|
myErrorTracker.capture(formattedMessage);
|
|
452
470
|
},
|
|
471
|
+
// staleDeployPatterns: [...], // customize substrings matched by isStaleDeployError;
|
|
472
|
+
// // defaults to defaultStaleDeployPatterns (chunk, failed to fetch, etc)
|
|
453
473
|
// overrideConsoleError: true, // route every console.error(...) call through onError too
|
|
454
474
|
// ignoreConsoleErrors: [...], // defaults to defaultIgnoredConsoleErrors (this package's
|
|
455
475
|
// // own Firebase Auth codes for expected user-input failures);
|
|
@@ -481,6 +501,37 @@ server-side resolution) on `ErrorHandlingParams` — reporting is skipped
|
|
|
481
501
|
whenever `consent` is set and not `true`, since sending error reports to a
|
|
482
502
|
third party without consent can itself be GDPR-relevant.
|
|
483
503
|
|
|
504
|
+
#### Stale Deploy & Chunk Load Error Recovery
|
|
505
|
+
|
|
506
|
+
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:
|
|
507
|
+
|
|
508
|
+
```typescript
|
|
509
|
+
import { isStaleDeployError, clearClientCache } from "cloudflare-next-intl/errorHandling";
|
|
510
|
+
|
|
511
|
+
export default function GlobalError({
|
|
512
|
+
error,
|
|
513
|
+
reset,
|
|
514
|
+
}: {
|
|
515
|
+
error: Error & { digest?: string };
|
|
516
|
+
reset: () => void;
|
|
517
|
+
}) {
|
|
518
|
+
useEffect(() => {
|
|
519
|
+
if (isStaleDeployError(error)) {
|
|
520
|
+
clearClientCache().then(() => {
|
|
521
|
+
window.location.reload();
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
}, [error]);
|
|
525
|
+
|
|
526
|
+
// ... render fallback UI
|
|
527
|
+
}
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
- `isStaleDeployError(error: Error, patterns?: readonly string[]): boolean`: Returns `true` if the error indicates a missing chunk, failed fetch, CSS chunk failure, closed connection, corrupted RSC payload, or hydration error #412 from a stale deployment. Defaults to `defaultStaleDeployPatterns` (or patterns configured in `intl-config.ts` via `errorHandling.staleDeployPatterns`).
|
|
531
|
+
- `setStaleDeployPatterns(patterns: readonly string[]): void`: Setter to update the active pattern list and pre-compute lowercased substrings for maximum runtime performance.
|
|
532
|
+
- `getStaleDeployPatterns(): readonly string[]`: Returns the currently active pattern list.
|
|
533
|
+
- `clearClientCache(): Promise<void>`: Best-effort cleanup that deletes all CacheStorage caches (`window.caches`), unregisters active Service Workers, and clears `sessionStorage`.
|
|
534
|
+
|
|
484
535
|
### Database (`db`)
|
|
485
536
|
|
|
486
537
|
Thin Postgres/Drizzle data-access layer over a Postgres connection string
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { setStaleDeployPatterns } from '../error_handling/is_stale_deploy_error';
|
|
1
2
|
// Every path this package compares against `request.nextUrl.pathname`
|
|
2
3
|
// (always `/`-prefixed) must itself start with `/` — a missing leading
|
|
3
4
|
// slash means `path === fa.verifyEmailPath` (and the same check for
|
|
@@ -58,5 +59,8 @@ function normalizeFirebaseAuthPaths(config) {
|
|
|
58
59
|
* ```
|
|
59
60
|
*/
|
|
60
61
|
export function setIntlConfig(config) {
|
|
62
|
+
if (config.errorHandling?.staleDeployPatterns) {
|
|
63
|
+
setStaleDeployPatterns(config.errorHandling.staleDeployPatterns);
|
|
64
|
+
}
|
|
61
65
|
return normalizeFirebaseAuthPaths(config);
|
|
62
66
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function clearClientCache(): Promise<void>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export default async function clearClientCache() {
|
|
2
|
+
try {
|
|
3
|
+
if (typeof window !== 'undefined' && 'caches' in window && window.caches) {
|
|
4
|
+
const keys = await caches.keys();
|
|
5
|
+
await Promise.all(keys.map((key) => caches.delete(key)));
|
|
6
|
+
}
|
|
7
|
+
if (typeof navigator !== 'undefined' && 'serviceWorker' in navigator && navigator.serviceWorker) {
|
|
8
|
+
const registrations = await navigator.serviceWorker.getRegistrations();
|
|
9
|
+
await Promise.all(registrations.map((registration) => registration.unregister()));
|
|
10
|
+
}
|
|
11
|
+
if (typeof sessionStorage !== 'undefined') {
|
|
12
|
+
sessionStorage.clear();
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
// best-effort cleanup, ignore failures
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -7,4 +7,6 @@ export { default as installGlobalErrorOverride } from './install_global_error_ov
|
|
|
7
7
|
export { default as stringifyUnknown } from './stringify_unknown';
|
|
8
8
|
export { default as formatErrorMessage } from './format_error_message';
|
|
9
9
|
export { defaultIgnoredConsoleErrors } from './default_ignored_console_errors';
|
|
10
|
+
export { default as isStaleDeployError, defaultStaleDeployPatterns, setStaleDeployPatterns, getStaleDeployPatterns, } from './is_stale_deploy_error';
|
|
11
|
+
export { default as clearClientCache } from './clear_client_cache';
|
|
10
12
|
export type { ErrorHandlingParams, ErrorHandlingRoutingConfig } from '../types/types';
|
|
@@ -5,3 +5,5 @@ export { default as installGlobalErrorOverride } from './install_global_error_ov
|
|
|
5
5
|
export { default as stringifyUnknown } from './stringify_unknown';
|
|
6
6
|
export { default as formatErrorMessage } from './format_error_message';
|
|
7
7
|
export { defaultIgnoredConsoleErrors } from './default_ignored_console_errors';
|
|
8
|
+
export { default as isStaleDeployError, defaultStaleDeployPatterns, setStaleDeployPatterns, getStaleDeployPatterns, } from './is_stale_deploy_error';
|
|
9
|
+
export { default as clearClientCache } from './clear_client_cache';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const defaultStaleDeployPatterns: readonly string[];
|
|
2
|
+
export declare function setStaleDeployPatterns(patterns: readonly string[]): void;
|
|
3
|
+
export declare function getStaleDeployPatterns(): readonly string[];
|
|
4
|
+
export default function isStaleDeployError(error: Error, patterns?: readonly string[]): boolean;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export const defaultStaleDeployPatterns = [
|
|
2
|
+
'chunk',
|
|
3
|
+
'failed to fetch',
|
|
4
|
+
'loading css chunk',
|
|
5
|
+
'connection closed',
|
|
6
|
+
'rsc payload',
|
|
7
|
+
'minified react error #412',
|
|
8
|
+
];
|
|
9
|
+
let activePatterns = defaultStaleDeployPatterns;
|
|
10
|
+
let activeLowercasedPatterns = defaultStaleDeployPatterns.map((p) => p.toLowerCase());
|
|
11
|
+
export function setStaleDeployPatterns(patterns) {
|
|
12
|
+
activePatterns = patterns;
|
|
13
|
+
activeLowercasedPatterns = patterns.map((p) => p.toLowerCase());
|
|
14
|
+
}
|
|
15
|
+
export function getStaleDeployPatterns() {
|
|
16
|
+
return activePatterns;
|
|
17
|
+
}
|
|
18
|
+
export default function isStaleDeployError(error, patterns) {
|
|
19
|
+
if (!error)
|
|
20
|
+
return false;
|
|
21
|
+
if (error.name === 'ChunkLoadError')
|
|
22
|
+
return true;
|
|
23
|
+
const message = (error.message || '').toLowerCase();
|
|
24
|
+
const list = patterns ? patterns.map((p) => p.toLowerCase()) : activeLowercasedPatterns;
|
|
25
|
+
for (let i = 0; i < list.length; i++) {
|
|
26
|
+
if (message.includes(list[i])) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
@@ -264,6 +264,13 @@ export interface ErrorHandlingRoutingConfig {
|
|
|
264
264
|
* default entirely; pass `[]` to report everything.
|
|
265
265
|
*/
|
|
266
266
|
ignoreConsoleErrors?: readonly string[];
|
|
267
|
+
/**
|
|
268
|
+
* Error message patterns matched by `isStaleDeployError(error)` to detect
|
|
269
|
+
* stale deploy / chunk load errors (e.g. `'chunk'`, `'failed to fetch'`,
|
|
270
|
+
* `'loading css chunk'`, `'connection closed'`, `'rsc payload'`,
|
|
271
|
+
* `'minified react error #412'`). Defaults to `defaultStaleDeployPatterns`.
|
|
272
|
+
*/
|
|
273
|
+
staleDeployPatterns?: readonly string[];
|
|
267
274
|
/**
|
|
268
275
|
* Called with the stringified message of each `console.error(...)` call
|
|
269
276
|
* (only consulted when `overrideConsoleError` is `true`), in addition to
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function buildIdAsset(fileName = "BUILD_ID") {
|
|
2
|
+
return {
|
|
3
|
+
name: "cfni:build-id-asset",
|
|
4
|
+
apply: "build",
|
|
5
|
+
generateBundle() {
|
|
6
|
+
if (this.environment?.name !== "client")
|
|
7
|
+
return;
|
|
8
|
+
const buildId = process.env.__VINEXT_SHARED_BUILD_ID ?? process.env.__VINEXT_BUILD_ID;
|
|
9
|
+
if (!buildId)
|
|
10
|
+
return;
|
|
11
|
+
this.emitFile({ type: "asset", fileName, source: buildId });
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
}
|
package/llms.txt
CHANGED
|
@@ -27,6 +27,11 @@ other subpath can be used.
|
|
|
27
27
|
- `./db` — `withPublicDb(fn)` / `withUserDb(fn, uid?)` server-side Postgres/Drizzle context helpers (require `db` set on your `RoutingConfig`; direct Postgres or Supabase Data API with automatic PostgREST REST translation and `cfni_exec` fallback, see below).
|
|
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
|
+
- `./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`.
|
|
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
|
+
- `./clearClientCache` — `clearClientCache()`: async helper wiping `window.caches`, unregistering service workers, and clearing `sessionStorage` for recovering from stale deployments.
|
|
34
|
+
- `./createServerErrorAction` — `createServerErrorAction(action, config)`: wrapper for server actions with standardized error reporting.
|
|
30
35
|
|
|
31
36
|
## `firebaseAuth*` subpaths (require `firebaseAuth` set on your `RoutingConfig`)
|
|
32
37
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cloudflare-next-intl",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.43",
|
|
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",
|
|
@@ -190,6 +190,14 @@
|
|
|
190
190
|
"types": "./dist/src/error_handling/create_server_error_action.d.ts",
|
|
191
191
|
"import": "./dist/src/error_handling/create_server_error_action.js"
|
|
192
192
|
},
|
|
193
|
+
"./isStaleDeployError": {
|
|
194
|
+
"types": "./dist/src/error_handling/is_stale_deploy_error.d.ts",
|
|
195
|
+
"import": "./dist/src/error_handling/is_stale_deploy_error.js"
|
|
196
|
+
},
|
|
197
|
+
"./clearClientCache": {
|
|
198
|
+
"types": "./dist/src/error_handling/clear_client_cache.d.ts",
|
|
199
|
+
"import": "./dist/src/error_handling/clear_client_cache.js"
|
|
200
|
+
},
|
|
193
201
|
"./db": {
|
|
194
202
|
"types": "./dist/src/db/index.d.ts",
|
|
195
203
|
"import": "./dist/src/db/index.js"
|
|
@@ -209,6 +217,10 @@
|
|
|
209
217
|
"./dbSchema": {
|
|
210
218
|
"types": "./dist/src/db/schema.d.ts",
|
|
211
219
|
"import": "./dist/src/db/schema.js"
|
|
220
|
+
},
|
|
221
|
+
"./vite": {
|
|
222
|
+
"types": "./dist/src/vite/build_id_asset.d.ts",
|
|
223
|
+
"import": "./dist/src/vite/build_id_asset.js"
|
|
212
224
|
}
|
|
213
225
|
},
|
|
214
226
|
"scripts": {
|
|
@@ -264,11 +276,15 @@
|
|
|
264
276
|
"peerDependencies": {
|
|
265
277
|
"next": ">=12.0.0",
|
|
266
278
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0",
|
|
267
|
-
"typescript": ">=5.0.0"
|
|
279
|
+
"typescript": ">=5.0.0",
|
|
280
|
+
"vite": ">=6"
|
|
268
281
|
},
|
|
269
282
|
"peerDependenciesMeta": {
|
|
270
283
|
"typescript": {
|
|
271
284
|
"optional": true
|
|
285
|
+
},
|
|
286
|
+
"vite": {
|
|
287
|
+
"optional": true
|
|
272
288
|
}
|
|
273
289
|
},
|
|
274
290
|
"devDependencies": {
|
|
@@ -292,6 +308,7 @@
|
|
|
292
308
|
"tsup": "^8.5.1",
|
|
293
309
|
"typescript": "^5.5.3",
|
|
294
310
|
"typescript-eslint": "^8.33.1",
|
|
311
|
+
"vite": "^7.0.0",
|
|
295
312
|
"vitest": "^3.0.8"
|
|
296
313
|
}
|
|
297
314
|
}
|