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 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 NextImage, { getImageProps as nextGetImageProps } from "next/image.js";
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(NextImage, { ...resolved });
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, autoAnalyticsEventsConfig: config.cookieConsent?.autoAnalyticsEvents, requiresConsent: requiresConsent, autoWireDialogs: config.cookieConsent?.autoWireDialogs !== false, dialogProps: config.cookieConsent?.dialogProps, updateDialogProps: config.cookieConsent?.updateDialogProps, children: children }) }) }));
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, autoAnalyticsEventsConfig: config.cookieConsent?.autoAnalyticsEvents, requiresConsent: requiresConsent, autoWireDialogs: config.cookieConsent?.autoWireDialogs !== false, dialogProps: config.cookieConsent?.dialogProps, updateDialogProps: config.cookieConsent?.updateDialogProps, children: children }) }));
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudflare-next-intl",
3
- "version": "0.10.32",
3
+ "version": "0.10.33",
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",