cloudflare-next-intl 0.6.14 → 0.6.16

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.
@@ -13,6 +13,64 @@ import type { TranslationObject } from "../../types/types";
13
13
  * otherwise.
14
14
  * @param messages Optional pre-loaded messages for `language`. If omitted,
15
15
  * they're loaded via `getMessage(language)`.
16
+ * @param staticSafe Marks THIS RENDER of `IntlProvider` as one that's safe
17
+ * to serve from static rendering / ISR — i.e. the caller already knows
18
+ * the current route never needs a server-resolved auth user (a public
19
+ * page: marketing, privacy policy, docs, etc). Concretely, setting this
20
+ * to `true` skips the internal `resolveAuthUserAndRedirect()` call.
21
+ *
22
+ * ── Why this call is normally made, and why skipping it is safe ──
23
+ * When `firebaseAuth` is configured, `IntlProvider` by default calls
24
+ * `resolveAuthUserAndRedirect()`, which:
25
+ * 1. Reads the session cookie via `cookies()` and verifies it against
26
+ * Firebase (server-side, authoritative check for "who is this?").
27
+ * 2. Reads the current pathname via `headers()` (`x-pathname`, set by
28
+ * `intlMiddleware`) to redirect guest → `redirectAuthPath` or
29
+ * signed-in → `homePath` on an auth page.
30
+ * 3. Returns the resolved user so the client `AuthUserProvider` can
31
+ * render the correct signed-in/signed-out UI on the FIRST paint,
32
+ * with zero flash.
33
+ * Both `cookies()` and `headers()` are request-scoped APIs — calling
34
+ * either one forces Next.js to render the ENTIRE subtree dynamically on
35
+ * every request. No static HTML, no ISR, no caching — for that route
36
+ * AND every route nested under this same `IntlProvider` call, whether
37
+ * or not that specific route actually needs auth. A page in
38
+ * `firebaseAuth.whiteListPaths` (meant to be public) is NOT exempt from
39
+ * this cost today: the whitelist check happens only AFTER `cookies()`/
40
+ * `headers()` are already read, so it's just as dynamic as a protected
41
+ * page.
42
+ *
43
+ * The redirect part of step 2 is redundant on any project using the
44
+ * default middleware wiring (`firebaseAuth.middlewareEnabled !== false`,
45
+ * the default): `intlMiddleware`'s `update_session` step already
46
+ * validates the session JWT (refreshing it via Firebase's token API if
47
+ * expired) and performs the exact same guest/auth-page redirects —
48
+ * authoritatively, on every request, BEFORE this component ever runs.
49
+ * So `staticSafe: true` does not weaken auth enforcement — the
50
+ * middleware gate still applies unchanged. The only thing you give up
51
+ * is step 3: `initialAuthUser` is not seeded server-side, so the client
52
+ * `AuthUserProvider` resolves it itself after mount instead. In
53
+ * practice this means a signed-in user MAY see this route's
54
+ * logged-out-state UI (e.g. a nav avatar placeholder) for one client
55
+ * render before the real user data appears — never wrong/protected
56
+ * content, since middleware already gated that; just a delayed value.
57
+ *
58
+ * ── When to use it ──
59
+ * Set `staticSafe: true` only on `IntlProvider` calls that wrap routes
60
+ * you know are always public and don't render auth-dependent UI above
61
+ * the fold (or can tolerate that UI appearing a moment late). Leave the
62
+ * default (`false`) for any `IntlProvider` call that also wraps
63
+ * protected routes or routes where the auth-state flash would be
64
+ * visually jarring (dashboards, account pages, anything showing
65
+ * `initialAuthUser`-derived content immediately). If you need
66
+ * different behavior for public vs protected routes within the SAME
67
+ * app, render `IntlProvider` twice — once per layout/route-group, each
68
+ * with its own `staticSafe` value — rather than picking one value for
69
+ * the whole app. If `firebaseAuth.middlewareEnabled` is explicitly
70
+ * `false` (middleware auth disabled), do NOT set `staticSafe: true` —
71
+ * this component becomes the ONLY place performing the auth redirect,
72
+ * so skipping it there really does remove the security check, not just
73
+ * the flash.
16
74
  *
17
75
  * @example
18
76
  * ```tsx
@@ -28,8 +86,9 @@ import type { TranslationObject } from "../../types/types";
28
86
  * }
29
87
  * ```
30
88
  */
31
- export default function LocationzationProvider({ language, messages, children }: {
89
+ export default function LocationzationProvider({ language, messages, staticSafe, children }: {
32
90
  language: string;
33
91
  messages?: TranslationObject;
92
+ staticSafe?: boolean;
34
93
  children: React.ReactNode;
35
94
  }): Promise<Component>;
@@ -23,6 +23,64 @@ let authUserServerProviderModule;
23
23
  * otherwise.
24
24
  * @param messages Optional pre-loaded messages for `language`. If omitted,
25
25
  * they're loaded via `getMessage(language)`.
26
+ * @param staticSafe Marks THIS RENDER of `IntlProvider` as one that's safe
27
+ * to serve from static rendering / ISR — i.e. the caller already knows
28
+ * the current route never needs a server-resolved auth user (a public
29
+ * page: marketing, privacy policy, docs, etc). Concretely, setting this
30
+ * to `true` skips the internal `resolveAuthUserAndRedirect()` call.
31
+ *
32
+ * ── Why this call is normally made, and why skipping it is safe ──
33
+ * When `firebaseAuth` is configured, `IntlProvider` by default calls
34
+ * `resolveAuthUserAndRedirect()`, which:
35
+ * 1. Reads the session cookie via `cookies()` and verifies it against
36
+ * Firebase (server-side, authoritative check for "who is this?").
37
+ * 2. Reads the current pathname via `headers()` (`x-pathname`, set by
38
+ * `intlMiddleware`) to redirect guest → `redirectAuthPath` or
39
+ * signed-in → `homePath` on an auth page.
40
+ * 3. Returns the resolved user so the client `AuthUserProvider` can
41
+ * render the correct signed-in/signed-out UI on the FIRST paint,
42
+ * with zero flash.
43
+ * Both `cookies()` and `headers()` are request-scoped APIs — calling
44
+ * either one forces Next.js to render the ENTIRE subtree dynamically on
45
+ * every request. No static HTML, no ISR, no caching — for that route
46
+ * AND every route nested under this same `IntlProvider` call, whether
47
+ * or not that specific route actually needs auth. A page in
48
+ * `firebaseAuth.whiteListPaths` (meant to be public) is NOT exempt from
49
+ * this cost today: the whitelist check happens only AFTER `cookies()`/
50
+ * `headers()` are already read, so it's just as dynamic as a protected
51
+ * page.
52
+ *
53
+ * The redirect part of step 2 is redundant on any project using the
54
+ * default middleware wiring (`firebaseAuth.middlewareEnabled !== false`,
55
+ * the default): `intlMiddleware`'s `update_session` step already
56
+ * validates the session JWT (refreshing it via Firebase's token API if
57
+ * expired) and performs the exact same guest/auth-page redirects —
58
+ * authoritatively, on every request, BEFORE this component ever runs.
59
+ * So `staticSafe: true` does not weaken auth enforcement — the
60
+ * middleware gate still applies unchanged. The only thing you give up
61
+ * is step 3: `initialAuthUser` is not seeded server-side, so the client
62
+ * `AuthUserProvider` resolves it itself after mount instead. In
63
+ * practice this means a signed-in user MAY see this route's
64
+ * logged-out-state UI (e.g. a nav avatar placeholder) for one client
65
+ * render before the real user data appears — never wrong/protected
66
+ * content, since middleware already gated that; just a delayed value.
67
+ *
68
+ * ── When to use it ──
69
+ * Set `staticSafe: true` only on `IntlProvider` calls that wrap routes
70
+ * you know are always public and don't render auth-dependent UI above
71
+ * the fold (or can tolerate that UI appearing a moment late). Leave the
72
+ * default (`false`) for any `IntlProvider` call that also wraps
73
+ * protected routes or routes where the auth-state flash would be
74
+ * visually jarring (dashboards, account pages, anything showing
75
+ * `initialAuthUser`-derived content immediately). If you need
76
+ * different behavior for public vs protected routes within the SAME
77
+ * app, render `IntlProvider` twice — once per layout/route-group, each
78
+ * with its own `staticSafe` value — rather than picking one value for
79
+ * the whole app. If `firebaseAuth.middlewareEnabled` is explicitly
80
+ * `false` (middleware auth disabled), do NOT set `staticSafe: true` —
81
+ * this component becomes the ONLY place performing the auth redirect,
82
+ * so skipping it there really does remove the security check, not just
83
+ * the flash.
26
84
  *
27
85
  * @example
28
86
  * ```tsx
@@ -38,7 +96,7 @@ let authUserServerProviderModule;
38
96
  * }
39
97
  * ```
40
98
  */
41
- export default async function LocationzationProvider({ language, messages, children }) {
99
+ export default async function LocationzationProvider({ language, messages, staticSafe = false, children }) {
42
100
  if (!localesSet.has(language)) {
43
101
  const { notFound } = await import("next/navigation");
44
102
  notFound();
@@ -54,10 +112,25 @@ export default async function LocationzationProvider({ language, messages, child
54
112
  let initialAuthUser = null;
55
113
  const autoWireClientProvider = config.firebaseAuth?.autoWireClientProvider !== false;
56
114
  if (config.firebaseAuth && autoWireClientProvider) {
57
- if (!authUserServerProviderModule) {
58
- authUserServerProviderModule = await import("../../firebase_auth/server/auth_user_server_provider");
115
+ // `staticSafe: true` with middleware auth disabled would silently
116
+ // drop the ONLY auth redirect this app has — not just the flash-
117
+ // prevention seed. Warn loudly rather than let that combination
118
+ // slip through unnoticed; still honor the caller's choice, since a
119
+ // hard throw here would be a worse failure mode than a console
120
+ // warning for what is, after all, a caller-controlled flag.
121
+ if (staticSafe && config.firebaseAuth.middlewareEnabled === false) {
122
+ console.warn('[cloudflare-next-intl] IntlProvider was called with `staticSafe: true` while ' +
123
+ '`firebaseAuth.middlewareEnabled` is `false`. With middleware auth disabled, ' +
124
+ 'this component is the ONLY place performing the auth redirect — skipping it ' +
125
+ 'here removes that protection entirely, it does not just remove a render flash. ' +
126
+ 'Set `staticSafe: false` (or enable middleware auth) for this route.');
127
+ }
128
+ if (!staticSafe) {
129
+ if (!authUserServerProviderModule) {
130
+ authUserServerProviderModule = await import("../../firebase_auth/server/auth_user_server_provider");
131
+ }
132
+ initialAuthUser = await authUserServerProviderModule.resolveAuthUserAndRedirect();
59
133
  }
60
- initialAuthUser = await authUserServerProviderModule.resolveAuthUserAndRedirect();
61
134
  }
62
135
  let analyticsConfig;
63
136
  let requiresConsent = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudflare-next-intl",
3
- "version": "0.6.14",
3
+ "version": "0.6.16",
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",