Skip to content

fix(ssg): avoid hydration mismatches on static root redirects#3964

Open
Mat4m0 wants to merge 2 commits intonuxt-modules:mainfrom
Mat4m0:fix/ssg-static-root-hydration
Open

fix(ssg): avoid hydration mismatches on static root redirects#3964
Mat4m0 wants to merge 2 commits intonuxt-modules:mainfrom
Mat4m0:fix/ssg-static-root-hydration

Conversation

@Mat4m0
Copy link
Copy Markdown

@Mat4m0 Mat4m0 commented Apr 23, 2026

🔗 Linked issue

📚 Description

This fixes a static-generation bug where browser language detection could switch locale and redirect while the prerendered page was still hydrating.

At a high level, the problem looked like this:

  • prefix_except_default

    • / is prerendered with the default locale HTML
    • on the first visit, the browser detects another locale
    • the client switches locale during hydration and then redirects
    • Vue reports a hydration mismatch
  • prefix

    • / is not prerendered as a real page
    • static hosts typically serve the prerender fallback shell (200.html) for /
    • browser detection still needs to happen there before Vue mounts

That early client-side locale flip also explains the related reports where:

  • localePath / NuxtLinkLocale links stayed in the prerendered locale
  • v-text, v-html, and translated attributes such as placeholder stayed stale until refresh
  • the first visit in a fresh browser session showed a mismatch, but later visits looked fine once the cookie existed

What changed

  1. During initial SSG hydration for prefixed strategies, we now keep the prerendered locale stable by only resolving locale from the route/domain.
  2. We skip the first SSG locale middleware pass so it cannot redirect during hydration.
  3. After mount, we run the normal browser-language detection once, sync the locale cookie, and navigate through the normal locale change path.
  4. For static hosting, we inject a small bootstrap redirect script into the prerendered root entry before Vue mounts:
    • /index.html for strategies where / is a real prerendered page
    • 200.html for strategy: 'prefix'

Simple examples

Before

defaultLocale: 'de', browser locale en, strategy: 'prefix_except_default'

  • prerendered / renders German HTML
  • client detects en during hydration
  • app redirects to /en
  • hydration mismatch is reported

After

  • prerendered / stays German through hydration
  • static root bootstrap redirects to /en before Vue mounts
  • localized links and translated attributes are already consistent when the app hydrates

For strategy: 'prefix', the same bootstrap logic now runs from the static fallback shell (200.html), which is the entry static hosts use for /.

Issue assessment

Test coverage

Added / expanded coverage for:

  • SSG prefix_except_default root redirect without hydration mismatch
  • SSG prefix fallback-shell redirect without hydration mismatch
  • localized links after redirect via both localePath and NuxtLinkLocale
  • translated v-text, v-html, and placeholder bindings after redirect

Targeted validation run:

  • pnpm test:types
  • pnpm exec eslint --no-warn-ignored src/runtime/plugins/route-locale-detect.ts src/runtime/plugins/ssg-detect.ts src/runtime/server/plugin.ts src/runtime/utils.ts
  • pnpm exec vitest run specs/issues/3988.spec.ts specs/issues/2790.spec.ts specs/browser_language_detection/prefix_except_default.spec.ts specs/browser_language_detection/prefix_and_default.spec.ts specs/issues/3407.spec.ts specs/ssg/no_prefix.spec.ts specs/ssg/different-domains.spec.ts specs/routing_strategies/prefix.spec.ts

Summary by CodeRabbit

  • New Features

    • Automatic locale detection and client redirect on prerendered sites; locale-aware routing and locale-prefixed home links.
    • Added English and German translations and a localized app template/page demonstrating translated headings and placeholders.
  • Bug Fixes

    • Improved locale initialization during static-site hydration to ensure correct language is displayed and synced.
  • Tests

    • Added end-to-end and unit tests covering prerendered locale detection, routing, and hydration behavior.

@Mat4m0 Mat4m0 requested a review from BobbieGoede as a code owner April 23, 2026 10:35
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 23, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 0d49929b-c20a-4f88-a403-b65cacc8e57e

📥 Commits

Reviewing files that changed from the base of the PR and between 146c4f6 and d1b1c80.

📒 Files selected for processing (3)
  • specs/issues/2790.spec.ts
  • specs/issues/3988.spec.ts
  • src/runtime/plugins/ssg-detect.ts
✅ Files skipped from review due to trivial changes (1)
  • specs/issues/2790.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • specs/issues/3988.spec.ts

Walkthrough

Adds a new fixture for issue #3988 (Nuxt app template, i18n locales en/de, page, nuxt.config, package.json) and two test suites validating prerendered i18n behavior. Runtime changes: compute an initial locale for SSG client hydration in route-locale-detect, update ssg-detect to load+set locale then navigate and set cookies, inject a server-side script for SSG root redirects, and add detectLocaleFromRouteOrDomain plus a syncCookie option to loadAndSetLocale.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main fix: avoiding hydration mismatches during SSG static root redirects, which is the core objective of this changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (7)
specs/fixtures/issues/3988/package.json (1)

9-12: Consider pinning fixture dependency versions for reproducibility.

Using "latest" for nuxt and @nuxtjs/i18n in a test fixture can make CI results non-deterministic if a future release regresses the behavior this fixture pins down. If other fixtures already use "latest", this is consistent; otherwise, pinning to known-good versions would make test failures easier to bisect.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@specs/fixtures/issues/3988/package.json` around lines 9 - 12, The
devDependencies in package.json currently use floating tags ("nuxt" and
"@nuxtjs/i18n" set to "latest") which makes the fixture non-deterministic;
change those two entries in package.json (the "nuxt" and "@nuxtjs/i18n" keys
under devDependencies) to explicit, pinned semantic versions (e.g., "x.y.z")
known-good for the fixture, then regenerate the lockfile (npm/yarn/pnpm install)
so CI uses reproducible installs; if other fixtures intentionally use "latest",
either align them or add a comment in this fixture explaining why it must be
pinned.
src/runtime/plugins/ssg-detect.ts (1)

18-24: Minor: ctx.setCookieLocale(locale) call on line 22 is likely redundant.

loadAndSetLocale (default syncCookie: true) routes through ctx.setLocaleSuspend, which already invokes ctx.setCookieLocale(locale) inside __resolvePendingLocalePromise (see src/runtime/context.ts:118-149). Leaving the explicit call is harmless and defensively guarantees the cookie is written even if the short-circuit at loadAndSetLocale’s top is hit (when detected === current locale and no pending switch), so it can stay — but a brief comment noting that intent would help future readers, or drop it if you prefer to rely solely on the suspend path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/plugins/ssg-detect.ts` around lines 18 - 24, The explicit
ctx.setCookieLocale(locale) after loadAndSetLocale is redundant because
loadAndSetLocale (via ctx.setLocaleSuspend -> __resolvePendingLocalePromise)
already writes the cookie; either remove the ctx.setCookieLocale(locale) call in
the nuxt.hook block or, if you want the defensive write retained, add a one-line
comment above it explaining that loadAndSetLocale/suspend path already sets the
cookie but this call is kept as a fallback. Locate the call in the nuxt.hook
where detectLocale, loadAndSetLocale, and navigate are used and apply one of the
two changes to keep intent clear.
specs/issues/2790.spec.ts (3)

53-66: Error path swallows the real failure and uses 500 for not-found.

The catch block returns 500 for every failure including ENOENT, and discards the error. If the test ever regresses (e.g. publicDir path changes, prerender output layout changes), debugging will be painful because the console shows only a generic 500. Log the error (at least console.error(err)) and/or distinguish ENOENT → 404 from other errors → 500.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@specs/issues/2790.spec.ts` around lines 53 - 66, The request handler's catch
block in the createServer callback swallows errors and always returns 500;
update the catch to log the caught error (e.g., console.error(err)) and
distinguish not-found errors by checking err.code === 'ENOENT' (return 404)
while keeping other errors as 500; apply this change around the
resolvePublicAsset/readFile usage in the createServer async handler so missing
public assets yield 404 and other failures are logged and return 500.

38-49: Describe block labeled #2790 but reuses the 3988 fixture.

The rootDir points to fixtures/issues/3988 while the suite name is #2790. This is intentional (reusing the fixture with a strategy: 'prefix' override), but it makes grepping for the fixture/spec relationship confusing, and a future change to the 3988 fixture can silently affect the 2790 regression. Consider either (a) adding a short comment here noting the shared fixture, or (b) extracting the shared page/locales into a common fixture directory and giving each issue its own nuxt.config.ts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@specs/issues/2790.spec.ts` around lines 38 - 49, The describe block named
"#2790" is reusing the fixture at fixtures/issues/3988 via the setup call
(rootDir: fileURLToPath(new URL(`../fixtures/issues/3988`, import.meta.url)))
which is confusing and brittle; add a short inline comment immediately above the
setup(...) call (or above the describe) stating that this spec intentionally
reuses the 3988 fixture and why (e.g., to override nuxtConfig.i18n.strategy to
'prefix'), or alternatively extract the shared pages/locales into a common
fixture and point rootDir to a new per-issue fixture with its own nuxt.config.ts
so changes to 3988 won’t silently affect `#2790` — reference the
describe('#2790'), setup(...), and rootDir lines when making the change.

16-36: Fallback handler returns 200 200.html for missing assets — may mask real 404s.

resolvePublicAsset falls through to 200.html for any unresolved path, and the handler then responds with statusCode = 200. For a missing JS chunk or locale file this would return an HTML shell with a 200 status, which the browser will try to parse as JS and can manifest as confusing hydration/console errors rather than a clear 404. Since 200.html is really only a SPA fallback for navigations, consider restricting the fallback to requests that accept HTML (or paths without a file extension), and returning 404 otherwise.

🔧 Suggested tightening
-  return join(publicDir, '200.html')
+  // Only use the SPA shell as fallback for navigations, not for asset requests.
+  if (!extname(pathname)) {
+    return join(publicDir, '200.html')
+  }
+  return null

…and have the request handler respond with 404 when null is returned.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@specs/issues/2790.spec.ts` around lines 16 - 36, resolvePublicAsset currently
falls back to returning 200.html for any missing path; change it so missing
paths with a file extension return null (indicating a 404) and only perform the
SPA fallback to 200.html for navigator-style requests (no extension) or the root
path. Concretely, update resolvePublicAsset to inspect pathname (e.g. via
path.extname) and if an extension exists and the exact/index files are missing,
return null instead of join(publicDir,'200.html'); keep existing behavior for
'/' and extensionless paths. Also update the request handler that calls
resolvePublicAsset to treat a null return as a 404 response (set statusCode =
404) rather than serving 200.html.
specs/issues/3988.spec.ts (2)

29-33: Hydration-warning assertion may miss Vue's actual warning channel.

Vue emits hydration mismatch warnings via console.warn, but in Playwright the message type for console.warn is reported as 'warning' — so this is correct. However, some hydration errors surface as 'error' type (e.g. [Vue warn]: Hydration ... mismatch) depending on Vue version/dev-vs-prod. Consider broadening to ['warning', 'error'].includes(log.type) to avoid false negatives.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@specs/issues/3988.spec.ts` around lines 29 - 33, The current assertion that
scans consoleLogs for hydration warnings only checks log.type === 'warning' and
can miss Vue hydration messages emitted as 'error'; update the expect block that
inspects consoleLogs (the variable used in the failing assertion) to treat both
'warning' and 'error' as candidates (e.g., replace the single-type check with an
inclusion check against ['warning','error'] when testing log.type) so hydration
mismatch messages reported as errors are also caught.

14-15: Brittle substring check for redirect injection.

Asserting the raw text window.location.replace( from the prerendered / response tightly couples the test to the current inline-script implementation in createStaticRootLocaleRedirectScript. If the script is refactored (e.g. renamed helper, split into a module, or minified), this assertion breaks without any behavioral regression. Consider asserting a more stable marker (e.g. a data-attribute on the injected <script>, or simply that the subsequent navigation lands on /en, which the rest of the test already covers).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@specs/issues/3988.spec.ts` around lines 14 - 15, The test's brittle check for
the literal string "window.location.replace(" should be replaced with a stable
assertion: either assert that the injected script element contains a stable
marker (e.g. a data attribute added by createStaticRootLocaleRedirectScript like
data-root-locale-redirect) or remove the substring assertion and instead verify
the behavioral outcome (that navigation ends up on /en). Update the spec in
specs/issues/3988.spec.ts to look for the script element with that data
attribute or assert the final navigation target rather than asserting the inline
helper implementation text.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/runtime/server/plugin.ts`:
- Around line 75-78: The bootstrap config currently assigns
detection.fallbackLocale directly (detection.fallbackLocale || '') which can be
an array or object and later breaks findSupportedLocale(String(...)); fix by
normalizing detection.fallbackLocale to a single locale string before
serializing (e.g., if detection.fallbackLocale is an array pick the first string
item, if it's an object pick the first value) so config.fallbackLocale is always
a string, or alternatively update the inline bootstrap logic that calls
findSupportedLocale to accept and iterate arrays/objects; update references in
this file to detection.fallbackLocale and config.fallbackLocale and ensure
findSupportedLocale is fed a normalized string.

---

Nitpick comments:
In `@specs/fixtures/issues/3988/package.json`:
- Around line 9-12: The devDependencies in package.json currently use floating
tags ("nuxt" and "@nuxtjs/i18n" set to "latest") which makes the fixture
non-deterministic; change those two entries in package.json (the "nuxt" and
"@nuxtjs/i18n" keys under devDependencies) to explicit, pinned semantic versions
(e.g., "x.y.z") known-good for the fixture, then regenerate the lockfile
(npm/yarn/pnpm install) so CI uses reproducible installs; if other fixtures
intentionally use "latest", either align them or add a comment in this fixture
explaining why it must be pinned.

In `@specs/issues/2790.spec.ts`:
- Around line 53-66: The request handler's catch block in the createServer
callback swallows errors and always returns 500; update the catch to log the
caught error (e.g., console.error(err)) and distinguish not-found errors by
checking err.code === 'ENOENT' (return 404) while keeping other errors as 500;
apply this change around the resolvePublicAsset/readFile usage in the
createServer async handler so missing public assets yield 404 and other failures
are logged and return 500.
- Around line 38-49: The describe block named "#2790" is reusing the fixture at
fixtures/issues/3988 via the setup call (rootDir: fileURLToPath(new
URL(`../fixtures/issues/3988`, import.meta.url))) which is confusing and
brittle; add a short inline comment immediately above the setup(...) call (or
above the describe) stating that this spec intentionally reuses the 3988 fixture
and why (e.g., to override nuxtConfig.i18n.strategy to 'prefix'), or
alternatively extract the shared pages/locales into a common fixture and point
rootDir to a new per-issue fixture with its own nuxt.config.ts so changes to
3988 won’t silently affect `#2790` — reference the describe('#2790'), setup(...),
and rootDir lines when making the change.
- Around line 16-36: resolvePublicAsset currently falls back to returning
200.html for any missing path; change it so missing paths with a file extension
return null (indicating a 404) and only perform the SPA fallback to 200.html for
navigator-style requests (no extension) or the root path. Concretely, update
resolvePublicAsset to inspect pathname (e.g. via path.extname) and if an
extension exists and the exact/index files are missing, return null instead of
join(publicDir,'200.html'); keep existing behavior for '/' and extensionless
paths. Also update the request handler that calls resolvePublicAsset to treat a
null return as a 404 response (set statusCode = 404) rather than serving
200.html.

In `@specs/issues/3988.spec.ts`:
- Around line 29-33: The current assertion that scans consoleLogs for hydration
warnings only checks log.type === 'warning' and can miss Vue hydration messages
emitted as 'error'; update the expect block that inspects consoleLogs (the
variable used in the failing assertion) to treat both 'warning' and 'error' as
candidates (e.g., replace the single-type check with an inclusion check against
['warning','error'] when testing log.type) so hydration mismatch messages
reported as errors are also caught.
- Around line 14-15: The test's brittle check for the literal string
"window.location.replace(" should be replaced with a stable assertion: either
assert that the injected script element contains a stable marker (e.g. a data
attribute added by createStaticRootLocaleRedirectScript like
data-root-locale-redirect) or remove the substring assertion and instead verify
the behavioral outcome (that navigation ends up on /en). Update the spec in
specs/issues/3988.spec.ts to look for the script element with that data
attribute or assert the final navigation target rather than asserting the inline
helper implementation text.

In `@src/runtime/plugins/ssg-detect.ts`:
- Around line 18-24: The explicit ctx.setCookieLocale(locale) after
loadAndSetLocale is redundant because loadAndSetLocale (via ctx.setLocaleSuspend
-> __resolvePendingLocalePromise) already writes the cookie; either remove the
ctx.setCookieLocale(locale) call in the nuxt.hook block or, if you want the
defensive write retained, add a one-line comment above it explaining that
loadAndSetLocale/suspend path already sets the cookie but this call is kept as a
fallback. Locate the call in the nuxt.hook where detectLocale, loadAndSetLocale,
and navigate are used and apply one of the two changes to keep intent clear.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 0f11d7b6-217e-493c-93c2-fcff25d05ffd

📥 Commits

Reviewing files that changed from the base of the PR and between 2e43980 and 146c4f6.

📒 Files selected for processing (12)
  • specs/fixtures/issues/3988/app.vue
  • specs/fixtures/issues/3988/i18n/locales/de.json
  • specs/fixtures/issues/3988/i18n/locales/en.json
  • specs/fixtures/issues/3988/nuxt.config.ts
  • specs/fixtures/issues/3988/package.json
  • specs/fixtures/issues/3988/pages/index.vue
  • specs/issues/2790.spec.ts
  • specs/issues/3988.spec.ts
  • src/runtime/plugins/route-locale-detect.ts
  • src/runtime/plugins/ssg-detect.ts
  • src/runtime/server/plugin.ts
  • src/runtime/utils.ts

Comment on lines +75 to +78
fallbackLocale: detection.fallbackLocale || '',
strategy: __I18N_STRATEGY__,
useCookie: detection.useCookie,
}
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

fallbackLocale may be an array; the bootstrap script only handles the string case.

In Nuxt i18n, the browser-detection fallbackLocale can be a string, an array of strings, or an object map. Here you coerce with detection.fallbackLocale || '' and later call findSupportedLocale(config.fallbackLocale), whose internal String(tag) on an array yields e.g. 'en,de' — which never matches any locale. For array/object fallbacks the bootstrap silently falls through to defaultLocale.

🛠️ Proposed fix — normalize to first-string form before serializing
-  const config = {
-    cookieKey: detection.cookieKey,
-    defaultLocale,
-    fallbackLocale: detection.fallbackLocale || '',
-    strategy: __I18N_STRATEGY__,
-    useCookie: detection.useCookie,
-  }
+  const normalizedFallback =
+    typeof detection.fallbackLocale === 'string'
+      ? detection.fallbackLocale
+      : Array.isArray(detection.fallbackLocale)
+        ? (detection.fallbackLocale[0] || '')
+        : ''
+  const config = {
+    cookieKey: detection.cookieKey,
+    defaultLocale,
+    fallbackLocale: normalizedFallback,
+    strategy: __I18N_STRATEGY__,
+    useCookie: detection.useCookie,
+  }

Or, pass an array through and iterate it inside the inline script.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/server/plugin.ts` around lines 75 - 78, The bootstrap config
currently assigns detection.fallbackLocale directly (detection.fallbackLocale ||
'') which can be an array or object and later breaks
findSupportedLocale(String(...)); fix by normalizing detection.fallbackLocale to
a single locale string before serializing (e.g., if detection.fallbackLocale is
an array pick the first string item, if it's an object pick the first value) so
config.fallbackLocale is always a string, or alternatively update the inline
bootstrap logic that calls findSupportedLocale to accept and iterate
arrays/objects; update references in this file to detection.fallbackLocale and
config.fallbackLocale and ensure findSupportedLocale is fed a normalized string.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied the test hardening suggestions that improve the PR without changing runtime behavior:

  • removed the brittle raw window.location.replace( assertion
  • broadened hydration log checks to cover both warning and error
  • tightened the custom static server in #2790 so missing asset requests return 404 instead of falling back to 200.html
  • added error logging in that test server
  • added a short note that #2790 intentionally reuses the #3988 fixture
  • added a clarifying comment on the explicit ctx.setCookieLocale(locale) call

I did not take the fallbackLocale suggestion. In this codepath we are using detectBrowserLanguage.fallbackLocale, which is typed as Locale | null, not the broader Vue i18n fallback-locale union. The proposed array/object normalization would add complexity for a case this API does not expose here.

I also left the fixture dependency versions as-is because this repo consistently uses latest across many fixtures, so pinning only this one would be inconsistent.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the type of fallbackLocale in the detection/i18n detection utilities
rg -n "fallbackLocale" src/runtime/shared/utils.ts src/runtime/shared/detection.ts src/module/utils.ts --type=ts -C3 2>/dev/null || \
rg -n "fallbackLocale" --type=ts -C2

Length of output: 14504


@Mat4m0, thanks for the detailed explanation and for verifying the type. You're correct — src/types.ts confirms that detectBrowserLanguage.fallbackLocale is typed as Locale | null, so it's always a string or null in this code path. My suggestion conflated it with Vue i18n's broader FallbackLocale union (which can be string, array, or object map), which is a separate concern. The detection.fallbackLocale || '' coercion is perfectly safe here. I'll record this distinction for future reviews.


✏️ Learnings added
Learnt from: Mat4m0
Repo: nuxt-modules/i18n PR: 3964
File: src/runtime/server/plugin.ts:75-78
Timestamp: 2026-04-23T11:22:23.684Z
Learning: In `src/runtime/server/plugin.ts` (nuxt-modules/i18n), `detection.fallbackLocale` used inside `createStaticRootLocaleRedirectScript` comes from `detectBrowserLanguage.fallbackLocale`, which is typed as `Locale | null` in `src/types.ts` — i.e., always a string or null. It is NOT the broader Vue i18n `FallbackLocale` union (string | string[] | object). Array/object normalization is therefore unnecessary in this context.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant