cloudflare-next-intl 0.9.21 → 0.9.23
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/dist/src/error_handling/is_stale_deploy_error.js +15 -4
- package/dist/src/error_handling/use_stale_deploy_recovery.js +17 -2
- package/dist/src/server/components/helper_script.js +12 -14
- package/dist/src/server/components/link.d.ts +4 -1
- package/dist/src/server/components/link.js +64 -11
- package/package.json +1 -1
|
@@ -8,6 +8,10 @@ export const defaultStaleDeployPatterns = [
|
|
|
8
8
|
'minified react error #412',
|
|
9
9
|
'the above error occurred in a react component',
|
|
10
10
|
'the connection to the page was unexpectedly closed',
|
|
11
|
+
'readablestream',
|
|
12
|
+
'readable stream',
|
|
13
|
+
'uncaught exception: undefined',
|
|
14
|
+
'uncaught undefined',
|
|
11
15
|
'server action not found',
|
|
12
16
|
'unrecognizedactionerror',
|
|
13
17
|
];
|
|
@@ -25,11 +29,18 @@ export default function isStaleDeployError(error, patterns) {
|
|
|
25
29
|
return true;
|
|
26
30
|
if (!error)
|
|
27
31
|
return false;
|
|
28
|
-
|
|
32
|
+
let message = '';
|
|
33
|
+
if (error instanceof Error) {
|
|
34
|
+
if (error.name === 'ChunkLoadError' || error.name === 'UnrecognizedActionError')
|
|
35
|
+
return true;
|
|
36
|
+
message = (error.message || '').toLowerCase();
|
|
37
|
+
}
|
|
38
|
+
else if (typeof error === 'string') {
|
|
39
|
+
message = error.toLowerCase();
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
29
42
|
return false;
|
|
30
|
-
|
|
31
|
-
return true;
|
|
32
|
-
const message = (error.message || '').toLowerCase();
|
|
43
|
+
}
|
|
33
44
|
const list = patterns ? patterns.map((p) => p.toLowerCase()) : activeLowercasedPatterns;
|
|
34
45
|
for (const pattern of list) {
|
|
35
46
|
if (message.includes(pattern)) {
|
|
@@ -44,9 +44,24 @@ function canRecover(error) {
|
|
|
44
44
|
try {
|
|
45
45
|
const reloadTimeRaw = sessionStorage.getItem(RECOVERY_TIME_KEY);
|
|
46
46
|
const reloadTime = reloadTimeRaw ? Number(reloadTimeRaw) : null;
|
|
47
|
-
|
|
47
|
+
const bId = currentBuildId();
|
|
48
|
+
const marker = sessionStorage.getItem(RECOVERY_RELOAD_KEY);
|
|
49
|
+
const isRecent = isRecentBuild(buildIdSetAt(), Date.now());
|
|
50
|
+
const isStale = isStaleDeployError(error);
|
|
51
|
+
const result = shouldRecoverFromStaleDeploy(error, bId, marker, isRecent, reloadTime, Date.now());
|
|
52
|
+
console.warn('[useStaleDeployRecovery]', {
|
|
53
|
+
error,
|
|
54
|
+
isStale,
|
|
55
|
+
bId,
|
|
56
|
+
marker,
|
|
57
|
+
isRecent,
|
|
58
|
+
reloadTime,
|
|
59
|
+
result,
|
|
60
|
+
});
|
|
61
|
+
return result;
|
|
48
62
|
}
|
|
49
|
-
catch {
|
|
63
|
+
catch (e) {
|
|
64
|
+
console.error('[useStaleDeployRecovery] Error in canRecover:', e);
|
|
50
65
|
return false;
|
|
51
66
|
}
|
|
52
67
|
}
|
|
@@ -23,23 +23,21 @@ export default function HelperScript() {
|
|
|
23
23
|
}
|
|
24
24
|
return false;
|
|
25
25
|
}
|
|
26
|
-
function recover(msg) {
|
|
27
|
-
// One reload attempt per page load, no matter how many matching
|
|
28
|
-
// errors fire in a row (a single stale deploy commonly throws
|
|
29
|
-
// several near-simultaneous chunk failures) — without this guard
|
|
30
|
-
// each one would race to read-then-write the same sessionStorage
|
|
31
|
-
// key and could re-trigger reload() multiple times before the
|
|
32
|
-
// first navigation lands.
|
|
26
|
+
function recover(msg, source) {
|
|
33
27
|
if (attemptedThisLoad) return;
|
|
34
28
|
try {
|
|
35
|
-
|
|
29
|
+
var stale = isStale(msg);
|
|
30
|
+
console.warn('[StaleDeploy early-catch] Intercepted:', { source: source, msg: msg, isStale: stale });
|
|
31
|
+
if (!stale) return;
|
|
36
32
|
var buildId = localStorage.getItem('buildId') || 'unknown';
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
33
|
+
var marker = sessionStorage.getItem(key);
|
|
34
|
+
if (marker === buildId) {
|
|
35
|
+
console.warn('[StaleDeploy early-catch] Skipping reload, already attempted for buildId:', buildId);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
41
38
|
attemptedThisLoad = true;
|
|
42
39
|
sessionStorage.setItem(key, buildId);
|
|
40
|
+
console.warn('[StaleDeploy early-catch] Reloading for buildId:', buildId);
|
|
43
41
|
try {
|
|
44
42
|
var u = new URL(window.location.href);
|
|
45
43
|
u.searchParams.set('_stale_reload', String(Date.now()));
|
|
@@ -51,9 +49,9 @@ export default function HelperScript() {
|
|
|
51
49
|
console.error('Stale Deploy Early Catch Script Error:', e);
|
|
52
50
|
}
|
|
53
51
|
}
|
|
54
|
-
window.addEventListener('error', function(e) { recover(e.message); });
|
|
52
|
+
window.addEventListener('error', function(e) { recover(e.message, 'error-event'); });
|
|
55
53
|
window.addEventListener('unhandledrejection', function(e) {
|
|
56
|
-
recover(e.reason && e.reason.message);
|
|
54
|
+
recover(e.reason && (e.reason.message || e.reason), 'unhandledrejection');
|
|
57
55
|
});
|
|
58
56
|
})();`
|
|
59
57
|
} }), !isDev &&
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { type LinkProps } from 'next/link.js';
|
|
2
2
|
import { type ComponentProps } from 'react';
|
|
3
|
-
type
|
|
3
|
+
export type PrefetchType = 'custom' | 'default';
|
|
4
|
+
type NextLinkProps = Omit<ComponentProps<'a'>, keyof LinkProps> & Omit<LinkProps, 'locale'> & {
|
|
5
|
+
prefetchType?: PrefetchType;
|
|
6
|
+
};
|
|
4
7
|
declare const Link: import("react").ForwardRefExoticComponent<Omit<NextLinkProps, "ref"> & import("react").RefAttributes<HTMLAnchorElement>>;
|
|
5
8
|
export default Link;
|
|
@@ -1,26 +1,79 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
3
|
import LinkComponent from 'next/link.js';
|
|
3
|
-
import { forwardRef, } from 'react';
|
|
4
|
+
import { forwardRef, useEffect, useState, useTransition, } from 'react';
|
|
4
5
|
import config from '../../config/intl_config.js';
|
|
5
6
|
import { getLocaleCache } from '../../general/cache_variables.js';
|
|
6
|
-
|
|
7
|
+
import { usePathname, useRouter } from 'next/navigation.js';
|
|
8
|
+
const prefetchedRoutes = new Set();
|
|
9
|
+
function CustomLinkFunction({ href, prefetch, prefetchType = 'custom', onClick, onMouseEnter, onPointerDown, ...rest }, ref) {
|
|
7
10
|
const localeValue = getLocaleCache();
|
|
11
|
+
const router = useRouter();
|
|
12
|
+
const pathname = usePathname();
|
|
13
|
+
const [isPending, startTransition] = useTransition();
|
|
14
|
+
const [isNavigating, setIsNavigating] = useState(false);
|
|
8
15
|
const needsLangPath = localeValue !== config.defaultLocale || !localeValue;
|
|
9
16
|
let pathnames;
|
|
17
|
+
let urlString;
|
|
10
18
|
if (needsLangPath) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
else {
|
|
16
|
-
pathname = href;
|
|
17
|
-
}
|
|
18
|
-
pathnames = `/${localeValue}${pathname}`;
|
|
19
|
+
const pathPart = typeof href === 'object' ? (href.pathname || '') : (href || '');
|
|
20
|
+
pathnames = `/${localeValue}${pathPart}`;
|
|
21
|
+
urlString = pathnames;
|
|
19
22
|
}
|
|
20
23
|
else {
|
|
21
24
|
pathnames = href;
|
|
25
|
+
urlString = typeof href === 'object' ? (href.pathname || '') : (href || '');
|
|
22
26
|
}
|
|
23
|
-
|
|
27
|
+
const isCustom = prefetchType === 'custom';
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
setIsNavigating(false);
|
|
30
|
+
}, [pathname]);
|
|
31
|
+
const triggerPrefetch = () => {
|
|
32
|
+
if (!isCustom || !urlString || urlString.startsWith('#') || prefetchedRoutes.has(urlString)) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
prefetchedRoutes.add(urlString);
|
|
36
|
+
try {
|
|
37
|
+
router.prefetch(typeof pathnames === 'string' ? pathnames : urlString);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (!isCustom)
|
|
44
|
+
return;
|
|
45
|
+
const timer = setTimeout(triggerPrefetch, 600);
|
|
46
|
+
return () => clearTimeout(timer);
|
|
47
|
+
}, [urlString, isCustom]);
|
|
48
|
+
const handleHoverPrefetch = () => {
|
|
49
|
+
if (isCustom) {
|
|
50
|
+
triggerPrefetch();
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
const handleClick = (e) => {
|
|
54
|
+
onClick?.(e);
|
|
55
|
+
if (e.defaultPrevented)
|
|
56
|
+
return;
|
|
57
|
+
if (isCustom && (isNavigating || isPending)) {
|
|
58
|
+
e.preventDefault();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (isCustom) {
|
|
62
|
+
setIsNavigating(true);
|
|
63
|
+
startTransition(() => {
|
|
64
|
+
router.push(typeof pathnames === 'string' ? pathnames : urlString);
|
|
65
|
+
});
|
|
66
|
+
e.preventDefault();
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const effectivePrefetch = isCustom ? (prefetch ?? false) : prefetch;
|
|
70
|
+
return _jsx(LinkComponent, { ref: ref, href: pathnames, prefetch: effectivePrefetch, onClick: handleClick, onMouseEnter: (e) => {
|
|
71
|
+
handleHoverPrefetch();
|
|
72
|
+
onMouseEnter?.(e);
|
|
73
|
+
}, onPointerDown: (e) => {
|
|
74
|
+
handleHoverPrefetch();
|
|
75
|
+
onPointerDown?.(e);
|
|
76
|
+
}, ...rest });
|
|
24
77
|
}
|
|
25
78
|
const Link = forwardRef(CustomLinkFunction);
|
|
26
79
|
export default Link;
|