cloudflare-next-intl 0.10.32 → 0.10.33
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 +1 -1
- package/dist/src/client/components/client_provider.js +1 -1
- package/dist/src/client/components/client_provider_static.js +1 -1
- package/dist/src/error_handling/use_stale_deploy_recovery.js +5 -2
- package/dist/src/image_optimizer/next_image_client.d.ts +10 -0
- package/dist/src/image_optimizer/next_image_client.js +30 -0
- package/dist/src/image_optimizer/next_image_shim.js +6 -5
- package/dist/src/server/components/server_provider.js +1 -1
- package/dist/src/server/components/server_provider_static.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -280,7 +280,7 @@ export default defineConfig({
|
|
|
280
280
|
|
|
281
281
|
Being text-only and transitive, it can over-flag: any local file the page reaches — however many imports away — that merely *calls* a recognized signal counts, even along a branch that never runs in production (a `Config.isDev`-gated `fetch(..., { cache: "no-store" })`) or one that's optional/best-effort (a `try`/`catch`-wrapped `getAuthUser()` used only to tag a log line). It cannot see that a call is conditional or swallowed. For a page whose only reason for being flagged is that kind of optional read — attaching "whoever's signed in, if anyone" to an error report, analytics event, or log line — switch that read to `resolveOptionalAuthUser()` (`cloudflare-next-intl/resolveOptionalAuthUser`, see Firebase Auth below): it wraps `getAuthUser()` the same way but, being an npm-package import, is a boundary the scan doesn't open, so it contributes no signal. If that read lives in a shared `onError` sink used by many pages at once, `resolveErrorReportingUser(useAuthUser?)` (same subpath) is the more precise fix: it's off (`{ user: null }`, no `getAuthUser()` call) by default, and only a `reportError({ ..., useAuthUser: true })` call site that actually wants the user on that report opts in per-call — instead of every page reaching that sink getting flagged.
|
|
282
282
|
2. **Auto Locale Params for Static Pages (`autoLocaleParams`)**: Automatically scans `page`/`layout`/`loading` files under a `[locale]`-scoped route during `configResolved` and inserts the `locale` param from `params` plus a `setLocale(locale)` call wherever `getTranslations()`/`useTranslations()` is used without one — the common cause of a page reading `NEXT_LOCALE` from cookies (and so implicitly becoming request-dependent) even though it never calls `cookies()` itself. Runs before `autoDynamicPages` so a page fixed this way no longer trips the cookie-derived-locale signal below. The scan (`checkLocaleParams`, also usable standalone from `cloudflare-next-intl/checkLocaleParams`, or via the `cfni-check-locale-params` CLI bin) is a text heuristic like `checkDynamicPages`; pass `mode: "report"` to only log without editing files, or `skip`/`overrides`/`localeParam` to fine-tune per-file.
|
|
283
|
-
4. **Build-Time Image Optimizer (`imageOptimizer`)**: Automatically scans your image directories (`public/images`, `public/icons`), downscales oversized assets, produces sibling formats (`webp` by default; also supports `avif`, `png`, `jpeg`, `gif`, `tiff`, `heif`, `jp2`, `jxl`), generates 8px `.blur.webp` thumbnails with Next.js-matching SVG Gaussian blur placeholders, and provides transparent `<Image placeholder="blur" />` shimming via virtual modules. When more than one format is generated for an image, the shim renders a `<picture>` with one `<source>` per format — ordered exactly as configured — so the browser picks the best format it supports, with the original untouched file as an `onError` fallback if a generated asset fails to load. When the same image is used at different widths across the codebase, each size gets its own generated variant, and each `<Image>` usage automatically resolves to the closest matching size. Remote, `data:` and `blob:` srcs (e.g. upload previews) are never matched against the manifest and pass through untouched. Runs on production build only by default (`dev: false`) — downscaling/format conversion is a build concern, and re-scanning on every dev server start slows cold starts for no dev-time benefit; the shim degrades cleanly with no manifest (plain, unoptimized `next/image` rendering, no blur placeholder). Pass `dev: true` to also run it in dev and preview real optimized output/blur.
|
|
283
|
+
4. **Build-Time Image Optimizer (`imageOptimizer`)**: Automatically scans your image directories (`public/images`, `public/icons`), downscales oversized assets, produces sibling formats (`webp` by default; also supports `avif`, `png`, `jpeg`, `gif`, `tiff`, `heif`, `jp2`, `jxl`), generates 8px `.blur.webp` thumbnails with Next.js-matching SVG Gaussian blur placeholders, and provides transparent `<Image placeholder="blur" />` shimming via virtual modules. When more than one format is generated for an image, the shim renders a `<picture>` with one `<source>` per format — ordered exactly as configured — so the browser picks the best format it supports, with the original untouched file as an `onError` fallback if a generated asset fails to load. When the same image is used at different widths across the codebase, each size gets its own generated variant, and each `<Image>` usage automatically resolves to the closest matching size. Remote, `data:` and `blob:` srcs (e.g. upload previews) are never matched against the manifest and pass through untouched. An auto-detected image loader is applied on the client, so `<Image>` stays safe in Server Components; a loader that throws falls back to the original `src`. Runs on production build only by default (`dev: false`) — downscaling/format conversion is a build concern, and re-scanning on every dev server start slows cold starts for no dev-time benefit; the shim degrades cleanly with no manifest (plain, unoptimized `next/image` rendering, no blur placeholder). Pass `dev: true` to also run it in dev and preview real optimized output/blur.
|
|
284
284
|
5. **Locale File Bundling & Resolution (`localeFiles`)**: Resolves `@locale-file/*` to your `./messages` directory and transforms dynamic imports into `import.meta.glob('/messages/*.json', { eager: true })` for lightning-fast locale loading on Cloudflare Workers.
|
|
285
285
|
6. **User-Agent Stub (`userAgentStub`)**: Prevents Next.js `user-agent` from importing `node:fs` during workerd runtime execution (which otherwise causes runtime 404 / 500 crashes in Workers proxy/middleware).
|
|
286
286
|
7. **Cloudflare Workers Client Stub (`cfWorkersClientStub`)**: Stubs `cloudflare:workers` in client builds so shared modules can be referenced without client bundling errors.
|
|
@@ -31,7 +31,7 @@ export default function LocationzationClientProvider({ language, messages, initi
|
|
|
31
31
|
providedChildren = _jsxs(AuthUserProvider, { initialUser: initialAuthUser, children: [children, config.firebaseAuth.performance !== false && (_jsx(Suspense, { fallback: null, children: _jsx(AutoFirebasePerformanceEvents, {}) }))] });
|
|
32
32
|
}
|
|
33
33
|
if (config.cookieConsent) {
|
|
34
|
-
providedChildren = _jsxs(CookieConsentProvider, { requiresConsent: requiresConsent, children: [providedChildren, analyticsConfig && (_jsx(Suspense, { fallback: null, children: _jsx(CookieConsentAnalytics, { config: analyticsConfig }) })), analyticsConfig && (analyticsConfig.googleAnalyticsId || analyticsConfig.googleAdsId) && (_jsx(Suspense, { fallback: null, children: _jsx(AutoAnalyticsEvents, { config: autoAnalyticsEventsConfig }) })), autoWireDialogs && (_jsx(Suspense, { fallback: null, children: _jsx(CookieConsentDialog, { ...dialogProps }) })), autoWireDialogs && (_jsx(Suspense, { fallback: null, children: _jsx(PrivacyPolicyUpdateDialog, { ...updateDialogProps }) }))] });
|
|
34
|
+
providedChildren = _jsxs(CookieConsentProvider, { requiresConsent: requiresConsent, children: [providedChildren, analyticsConfig && (_jsx(Suspense, { fallback: null, children: _jsx(CookieConsentAnalytics, { config: analyticsConfig }) })), analyticsConfig && (analyticsConfig.googleAnalyticsId || analyticsConfig.googleAdsId) && (_jsx(Suspense, { fallback: null, children: _jsx(AutoAnalyticsEvents, { config: autoAnalyticsEventsConfig ?? config.cookieConsent?.autoAnalyticsEvents }) })), autoWireDialogs && (_jsx(Suspense, { fallback: null, children: _jsx(CookieConsentDialog, { ...(dialogProps ?? config.cookieConsent?.dialogProps) }) })), autoWireDialogs && (_jsx(Suspense, { fallback: null, children: _jsx(PrivacyPolicyUpdateDialog, { ...(updateDialogProps ?? config.cookieConsent?.updateDialogProps) }) }))] });
|
|
35
35
|
}
|
|
36
36
|
const contextValue = useMemo(() => ({ language, messages }), [language, messages]);
|
|
37
37
|
return _jsx(LocaleContext.Provider, { value: contextValue, children: providedChildren });
|
|
@@ -19,7 +19,7 @@ export default function LocationzationClientProvider({ language, messages, analy
|
|
|
19
19
|
installGlobalErrorOverride(config);
|
|
20
20
|
let providedChildren = children;
|
|
21
21
|
if (config.cookieConsent) {
|
|
22
|
-
providedChildren = _jsxs(CookieConsentProvider, { requiresConsent: requiresConsent, children: [providedChildren, analyticsConfig && (_jsx(Suspense, { fallback: null, children: _jsx(CookieConsentAnalytics, { config: analyticsConfig }) })), analyticsConfig && (analyticsConfig.googleAnalyticsId || analyticsConfig.googleAdsId) && (_jsx(Suspense, { fallback: null, children: _jsx(AutoAnalyticsEvents, { config: autoAnalyticsEventsConfig }) })), autoWireDialogs && (_jsx(Suspense, { fallback: null, children: _jsx(CookieConsentDialog, { ...dialogProps }) })), autoWireDialogs && (_jsx(Suspense, { fallback: null, children: _jsx(PrivacyPolicyUpdateDialog, { ...updateDialogProps }) }))] });
|
|
22
|
+
providedChildren = _jsxs(CookieConsentProvider, { requiresConsent: requiresConsent, children: [providedChildren, analyticsConfig && (_jsx(Suspense, { fallback: null, children: _jsx(CookieConsentAnalytics, { config: analyticsConfig }) })), analyticsConfig && (analyticsConfig.googleAnalyticsId || analyticsConfig.googleAdsId) && (_jsx(Suspense, { fallback: null, children: _jsx(AutoAnalyticsEvents, { config: autoAnalyticsEventsConfig ?? config.cookieConsent?.autoAnalyticsEvents }) })), autoWireDialogs && (_jsx(Suspense, { fallback: null, children: _jsx(CookieConsentDialog, { ...(dialogProps ?? config.cookieConsent?.dialogProps) }) })), autoWireDialogs && (_jsx(Suspense, { fallback: null, children: _jsx(PrivacyPolicyUpdateDialog, { ...(updateDialogProps ?? config.cookieConsent?.updateDialogProps) }) }))] });
|
|
23
23
|
}
|
|
24
24
|
const contextValue = useMemo(() => ({ language, messages }), [language, messages]);
|
|
25
25
|
return _jsx(LocaleContext.Provider, { value: contextValue, children: providedChildren });
|
|
@@ -119,11 +119,14 @@ export default function useStaleDeployRecovery(error, onRecover, delayMs = 1000)
|
|
|
119
119
|
return;
|
|
120
120
|
const buildId = currentBuildId();
|
|
121
121
|
const timeout = setTimeout(() => {
|
|
122
|
+
let spent = 0;
|
|
123
|
+
try {
|
|
124
|
+
spent = currentAttempts(buildId, sessionStorage.getItem(RECOVERY_RELOAD_KEY));
|
|
125
|
+
}
|
|
126
|
+
catch { }
|
|
122
127
|
Promise.all([initialOnRecover?.().catch(() => undefined), clearClientCache().catch(() => undefined)])
|
|
123
128
|
.finally(() => {
|
|
124
129
|
try {
|
|
125
|
-
const marker = sessionStorage.getItem(RECOVERY_RELOAD_KEY);
|
|
126
|
-
const spent = currentAttempts(buildId, marker);
|
|
127
130
|
sessionStorage.setItem(RECOVERY_RELOAD_KEY, buildId);
|
|
128
131
|
sessionStorage.setItem(RECOVERY_COUNT_KEY, String(spent + 1));
|
|
129
132
|
sessionStorage.setItem(RECOVERY_TIME_KEY, String(Date.now()));
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { ImageProps } from "next/image.js";
|
|
3
|
+
type Loader = (props: {
|
|
4
|
+
src: string;
|
|
5
|
+
width: number;
|
|
6
|
+
quality?: number;
|
|
7
|
+
}) => string;
|
|
8
|
+
export declare function safeLoader(loader: Loader): Loader;
|
|
9
|
+
export declare function NextImageClient(props: ImageProps): React.JSX.Element;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import NextImage from "next/image.js";
|
|
4
|
+
let defaultCustomLoader;
|
|
5
|
+
try {
|
|
6
|
+
const loaderMod = (await import("virtual:cloudflare-next-intl-image-loader"));
|
|
7
|
+
const candidate = loaderMod.defaultLoader ?? loaderMod.default;
|
|
8
|
+
defaultCustomLoader = typeof candidate === "function" ? candidate : undefined;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
defaultCustomLoader = undefined;
|
|
12
|
+
}
|
|
13
|
+
export function safeLoader(loader) {
|
|
14
|
+
return (params) => {
|
|
15
|
+
try {
|
|
16
|
+
const url = loader(params);
|
|
17
|
+
if (typeof url === "string" && url !== "")
|
|
18
|
+
return url;
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
console.error("[cloudflare-next-intl] image loader failed, using original src:", params.src, error);
|
|
22
|
+
}
|
|
23
|
+
return params.src;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
const safeDefaultLoader = defaultCustomLoader ? safeLoader(defaultCustomLoader) : undefined;
|
|
27
|
+
export function NextImageClient(props) {
|
|
28
|
+
const loader = typeof props.loader === "function" ? safeLoader(props.loader) : safeDefaultLoader;
|
|
29
|
+
return _jsx(NextImage, { ...props, loader: loader });
|
|
30
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import
|
|
2
|
+
import { getImageProps as nextGetImageProps } from "next/image.js";
|
|
3
3
|
import { getImageBlurSvg } from "./blur_svg.js";
|
|
4
4
|
import { ImgWithFallback } from "./img_with_fallback.js";
|
|
5
|
+
import { NextImageClient } from "./next_image_client.js";
|
|
5
6
|
function pickVariant(entry, requestedWidth) {
|
|
6
7
|
if (!requestedWidth || !entry.variants || entry.variants.length === 0) {
|
|
7
8
|
return entry;
|
|
@@ -96,13 +97,13 @@ function toPlainImgAttrs(resolved, src) {
|
|
|
96
97
|
fetchPriority: priority ? "high" : "auto",
|
|
97
98
|
};
|
|
98
99
|
}
|
|
99
|
-
function resolveProps(props) {
|
|
100
|
+
function resolveProps(props, injectLoader = false) {
|
|
100
101
|
let src = props.src;
|
|
101
102
|
let blurDataURL = props.blurDataURL;
|
|
102
103
|
let width = props.width;
|
|
103
104
|
let height = props.height;
|
|
104
105
|
let style = props.style;
|
|
105
|
-
const loader = props.loader ?? defaultCustomLoader;
|
|
106
|
+
const loader = injectLoader ? (props.loader ?? defaultCustomLoader) : props.loader;
|
|
106
107
|
const entry = findEntry(src);
|
|
107
108
|
if (entry) {
|
|
108
109
|
const variant = pickVariant(entry, typeof props.width === "number" ? props.width : undefined);
|
|
@@ -145,7 +146,7 @@ export default function Image(props) {
|
|
|
145
146
|
: undefined;
|
|
146
147
|
const alternates = (variant?.sources ?? []).filter((source) => source.src !== variant?.src);
|
|
147
148
|
if (alternates.length === 0) {
|
|
148
|
-
return _jsx(
|
|
149
|
+
return _jsx(NextImageClient, { ...resolved });
|
|
149
150
|
}
|
|
150
151
|
const primarySrc = variant?.src ?? String(resolved.src);
|
|
151
152
|
const originalSrc = entry?.originalSrc;
|
|
@@ -153,7 +154,7 @@ export default function Image(props) {
|
|
|
153
154
|
return (_jsxs("picture", { children: [alternates.map((source) => (_jsx("source", { type: source.type, sizes: imgProps.sizes, srcSet: source.src }, source.src))), _jsx(ImgWithFallback, { ...imgProps, originalSrc: originalSrc })] }));
|
|
154
155
|
}
|
|
155
156
|
export function getImageProps(props) {
|
|
156
|
-
const resolved = resolveProps(props);
|
|
157
|
+
const resolved = resolveProps(props, true);
|
|
157
158
|
return nextGetImageProps(resolved);
|
|
158
159
|
}
|
|
159
160
|
export { getImageBlurSvg };
|
|
@@ -61,5 +61,5 @@ export default async function LocationzationProvider({ language, messages, stati
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
-
return (_jsx(LocaleProvider, { language: language, messages: messagesValue, children: _jsx(Suspense, { fallback: children, children: _jsx(LocationzationClientProvider, { language: language, messages: messagesValue, initialAuthUser: initialAuthUser, skipAuthProvider: !autoWireClientProvider, analyticsConfig: analyticsConfig,
|
|
64
|
+
return (_jsx(LocaleProvider, { language: language, messages: messagesValue, children: _jsx(Suspense, { fallback: children, children: _jsx(LocationzationClientProvider, { language: language, messages: messagesValue, initialAuthUser: initialAuthUser, skipAuthProvider: !autoWireClientProvider, analyticsConfig: analyticsConfig, requiresConsent: requiresConsent, autoWireDialogs: config.cookieConsent?.autoWireDialogs !== false, children: children }) }) }));
|
|
65
65
|
}
|
|
@@ -49,5 +49,5 @@ export default async function LocationzationProvider({ language, messages, child
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
-
return (_jsx(Suspense, { fallback: children, children: _jsx(LocationzationClientProvider, { language: language, messages: messagesValue, analyticsConfig: analyticsConfig,
|
|
52
|
+
return (_jsx(Suspense, { fallback: children, children: _jsx(LocationzationClientProvider, { language: language, messages: messagesValue, analyticsConfig: analyticsConfig, requiresConsent: requiresConsent, autoWireDialogs: config.cookieConsent?.autoWireDialogs !== false, children: children }) }));
|
|
53
53
|
}
|