[WIP] feat: runtime request config (proof of concept) - #4119
Conversation
commit: |
WalkthroughThe runtime now supports per-request locale configuration through the Estimated code review effort: 4 (Complex) | ~45 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
specs/request_config/request_config.spec.ts (1)
54-61: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy liftExercise the hydrated client configuration.
This test only inspects the server-rendered
window.__NUXT__.configstring. It does not run client hydration. A serialization assertion cannot detect a client-side mismatch inlocaleCodes,locales, orswitchLocalePath.Start the fixture with browser support. Then assert the rendered values after the restricted-host page hydrates. This verifies the hydration behavior described by this test and the PR objective.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@specs/request_config/request_config.spec.ts` around lines 54 - 61, Update the test around getHydratedConfig to launch the fixture with browser support and perform client hydration for the restricted host page. Assert the hydrated rendered values for localeCodes, locales, and switchLocalePath, while preserving the expected restricted-language behavior that includes nl-NL and excludes fr-FR.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/runtime/shared/locales.ts`:
- Around line 90-100: Update resolveRequestLocales so config.locales is always
assigned the normalized resolved list, moving config.locales = resolved outside
the resolved.length !== codes.size conditional. Keep the existing development
warning and unknown-locale filtering inside the conditional.
- Around line 102-105: Update the prerender narrowing warning condition near
warnedPrerenderNarrowing to remove the import.meta.dev requirement and rely only
on import.meta.prerender, while preserving the existing locale-length check,
one-time warning guard, and warning message.
---
Nitpick comments:
In `@specs/request_config/request_config.spec.ts`:
- Around line 54-61: Update the test around getHydratedConfig to launch the
fixture with browser support and perform client hydration for the restricted
host page. Assert the hydrated rendered values for localeCodes, locales, and
switchLocalePath, while preserving the expected restricted-language behavior
that includes nl-NL and excludes fr-FR.
🪄 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 Plus
Run ID: e63f22d3-abaa-43de-ad71-0d225267ba30
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (20)
playground/i18n/locales/nl.jsonplayground/nuxt.config.tsplayground/server/plugins/i18n-request-config.tsspecs/fixtures/request_config/i18n/i18n.config.tsspecs/fixtures/request_config/nuxt.config.tsspecs/fixtures/request_config/package.jsonspecs/fixtures/request_config/pages/index.vuespecs/fixtures/request_config/server/plugins/i18n-request-config.tsspecs/request_config/request_config.spec.tssrc/runtime/context.tssrc/runtime/plugins/i18n.tssrc/runtime/routing/runtime-locales.tssrc/runtime/server/context.tssrc/runtime/server/plugin.tssrc/runtime/shared/detection.tssrc/runtime/shared/locales.tssrc/runtime/utils.tssrc/types.tstest/detection.test.tstest/redirect.test.ts
| export function resolveRequestLocales(config: I18nPublicRuntimeConfig): NormalizedLocaleObject[] { | ||
| const codes = new Set(config.locales.map(locale => (isString(locale) ? locale : locale.code))) | ||
| const resolved = normalizedLocales.filter(locale => codes.has(locale.code)) | ||
|
|
||
| if (resolved.length !== codes.size) { | ||
| if (import.meta.dev) { | ||
| const unknown = [...codes].filter(code => !resolved.some(l => l.code === code)) | ||
| console.warn(`[nuxt-i18n] Ignoring locales that are not part of the build: ${unknown.join(', ')}`) | ||
| } | ||
| config.locales = resolved | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Normalize config.locales unconditionally, not only when codes are pruned.
config.locales = resolved only runs if (resolved.length !== codes.size). When the i18n:request-config hook narrows locales using valid plain-code strings (for example config.locales = ['en', 'nl']), no codes are dropped, so this branch is skipped and config.locales keeps holding raw strings.
composer.locales in src/runtime/plugins/i18n.ts reads runtimeI18n.locales (this same object) directly and exposes it as $i18n.locales. For this request, it would then return plain strings instead of LocaleObjects, while ctx.getLocales()/requestLocales stay fully normalized. Code that reads $i18n.locales[i].name, .dir, or .domains breaks silently for that request.
Move the assignment outside the if so config.locales always reflects the normalized list.
Proposed fix
const resolved = normalizedLocales.filter(locale => codes.has(locale.code))
- if (resolved.length !== codes.size) {
- if (import.meta.dev) {
- const unknown = [...codes].filter(code => !resolved.some(l => l.code === code))
- console.warn(`[nuxt-i18n] Ignoring locales that are not part of the build: ${unknown.join(', ')}`)
- }
- config.locales = resolved
+ if (import.meta.dev && resolved.length !== codes.size) {
+ const unknown = [...codes].filter(code => !resolved.some(l => l.code === code))
+ console.warn(`[nuxt-i18n] Ignoring locales that are not part of the build: ${unknown.join(', ')}`)
}
+ // keep `config.locales` normalized regardless of pruning, so readers such as `composer.locales`
+ // never see raw locale-code strings for this request
+ config.locales = resolved📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export function resolveRequestLocales(config: I18nPublicRuntimeConfig): NormalizedLocaleObject[] { | |
| const codes = new Set(config.locales.map(locale => (isString(locale) ? locale : locale.code))) | |
| const resolved = normalizedLocales.filter(locale => codes.has(locale.code)) | |
| if (resolved.length !== codes.size) { | |
| if (import.meta.dev) { | |
| const unknown = [...codes].filter(code => !resolved.some(l => l.code === code)) | |
| console.warn(`[nuxt-i18n] Ignoring locales that are not part of the build: ${unknown.join(', ')}`) | |
| } | |
| config.locales = resolved | |
| } | |
| export function resolveRequestLocales(config: I18nPublicRuntimeConfig): NormalizedLocaleObject[] { | |
| const codes = new Set(config.locales.map(locale => (isString(locale) ? locale : locale.code))) | |
| const resolved = normalizedLocales.filter(locale => codes.has(locale.code)) | |
| if (import.meta.dev && resolved.length !== codes.size) { | |
| const unknown = [...codes].filter(code => !resolved.some(l => l.code === code)) | |
| console.warn(`[nuxt-i18n] Ignoring locales that are not part of the build: ${unknown.join(', ')}`) | |
| } | |
| // keep `config.locales` normalized regardless of pruning, so readers such as `composer.locales` | |
| // never see raw locale-code strings for this request | |
| config.locales = resolved |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/runtime/shared/locales.ts` around lines 90 - 100, Update
resolveRequestLocales so config.locales is always assigned the normalized
resolved list, moving config.locales = resolved outside the resolved.length !==
codes.size conditional. Keep the existing development warning and unknown-locale
filtering inside the conditional.
| if (import.meta.dev && import.meta.prerender && !warnedPrerenderNarrowing && resolved.length !== localeCodes.length) { | ||
| warnedPrerenderNarrowing = true | ||
| console.warn('[nuxt-i18n] `i18n:request-config` narrowed the locales while prerendering - there is no request host at generate time, so this decision is frozen into every prerendered page.') | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate locales.ts =="
git ls-files | rg '(^|/)locales\.ts$' || true
echo
echo "== inspect target file relevant section =="
if [ -f src/runtime/shared/locales.ts ]; then
nl -ba src/runtime/shared/locales.ts | sed -n '1,160p'
fi
echo
echo "== search import.meta.dev/prerender usages =="
rg -n "import\.meta\.(dev|prerender)" -S . || true
echo
echo "== package/config hints =="
git ls-files | rg '(^|/)(package\.json|nuxt\.config\.(ts|js|mjs)|.nuxtignore)$' || true
sed -n '1,220p' package.json 2>/dev/null || trueRepository: nuxt-modules/i18n
Length of output: 294
🌐 Web query:
Nuxt import.meta.dev import.meta.prerender generate production mode Nuxt options
💡 Result:
In Nuxt, import.meta properties are tree-shakable build flags injected during the build process to provide runtime context about your application environment [1][2][3]. import.meta.dev This flag is true when running the Nuxt development server and false in production builds [1][2]. It is intended for use in runtime code (components, plugins, middleware, etc.) [4][5]. It is not available in configuration files like nuxt.config.ts or modules, where you should instead use environment variables or standard Node.js checks like import.meta.env.NODE_ENV === 'development' [4][5]. import.meta.prerender This flag is true only during the server-side prerendering stage of a build [1][2]. It allows your code to detect when it is being executed as part of the static generation process (e.g., triggered by npx nuxt generate or npx nuxt build --prerender) [6][7][8]. Recent updates have ensured this flag is correctly preserved in server builds so that it can be accurately evaluated by the Nitro engine during prerendering [9]. Production Mode and Generation When you run npx nuxt generate, Nuxt effectively executes a build and then performs prerendering [6][7]. This command pre-renders your application routes into static HTML files [6][7]. In this mode, import.meta.prerender will be true while individual pages are being generated [1][2]. Note that import.meta flags are explicitly designed for runtime usage [1][2]. If you need to change behavior based on the build mode within your nuxt.config.ts, you should use Nuxt configuration options or environment variables, as build-time flags are replaced statically during the bundling process [4][5][3].
Citations:
- 1: https://nuxt.com/docs/4.x/api/advanced/import-meta
- 2: https://nuxt.com/docs/3.x/api/advanced/import-meta
- 3: feat(vite,webpack): tree-shakable
import.meta.*build flags nuxt/nuxt#22428 - 4: 4.2.0 import.meta.dev is undefined nuxt/nuxt#33600
- 5:
import.meta.devis undefined innuxt.config.tsw/nuxt devnuxt/nuxt#32098 - 6: https://nuxt.com/docs/4.x/api/commands/generate
- 7: https://nuxt.com/docs/3.x/api/commands/generate
- 8: https://nuxt.com/docs/3.x/api/commands/build
- 9: fix(webpack,rspack): preserve prerender + nitro flags in server builds nuxt/nuxt#33503
🌐 Web query:
Nuxt 3 nuxi generate prerender production mode import meta prerender
💡 Result:
In Nuxt 3, import.meta.prerender is a boolean flag that is true when your code is running on the server during the prerendering stage of a build [1]. This stage is triggered when using the nuxi generate command [2][3] or running nuxi build --prerender [4]. ### How it works During the generation process, Nuxt uses the Nitro crawler to discover and render your application routes into static HTML files [5][6]. When import.meta.prerender is true, it indicates that the current execution context is part of this static build-time rendering, rather than a standard server-side rendering (SSR) request at runtime [1]. ### Common use cases 1. Conditional logic: You can use this flag to execute code specifically during the build process, such as fetching static data or configuring plugins, while ensuring that code is tree-shaken (excluded) from your final client-side bundle [7]. 2. Payload optimization: It is often used to manage shared data or state. For example, you can use it to determine whether to fetch data from an external API or to use a pre-extracted payload that is available only during the prerendering phase [7]. 3. Route hinting: You can combine this with runtime composables like prerenderRoutes to programmatically signal to Nitro that specific dynamic routes should be prerendered [8]. ### Relationship to other modes - Production vs. Development: The import.meta.prerender flag is distinct from import.meta.dev [1]. Even in a production build, import.meta.prerender is only true during the phase where static assets are generated [1]. - Server-side vs. Client-side: While import.meta.server is true during both standard SSR and prerendering, import.meta.prerender specifically isolates the static generation step [1]. By leveraging this flag, you can optimize your application's bundle size by ensuring that server-only build logic does not leak into the browser [7].
Citations:
- 1: https://nuxt.com/docs/3.x/api/advanced/import-meta
- 2: https://nuxt.com/docs/3.x/api/commands/generate
- 3: https://github.com/nuxt/cli/blob/main/packages/nuxi/src/commands/generate.ts
- 4: https://nuxt.com/docs/3.x/api/commands/build
- 5: https://nuxt.com/docs/3.x/getting-started/prerendering
- 6: https://nuxt.com/docs/4.x/getting-started/prerendering
- 7: https://dev.to/danielroe/using-shared-data-when-generating-pages-45ok
- 8: https://github.com/nuxt/nuxt/blob/main/packages/nuxt/src/app/composables/ssr.ts
Remove the import.meta.dev guard from the prerender narrowing warning.
nuxi generate runs prerendering with import.meta.prerender set, not dev mode. Use only import.meta.prerender so the warning fires when locales are narrowed during generate-time prerendering.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/runtime/shared/locales.ts` around lines 102 - 105, Update the prerender
narrowing warning condition near warnedPrerenderNarrowing to remove the
import.meta.dev requirement and rely only on import.meta.prerender, while
preserving the existing locale-length check, one-time warning guard, and warning
message.
|
A consumer datapoint for the "which subset is honestly supportable" question, since #4016 is mine and you listed it in this family. Our shape. One image, seven regional deployments, 13 locales in the build, four distinct default locales across those deployments ( The part that matters for scoping: for us the config is per-process, not per-request. Each pod serves one region and is pinned at boot by env ( That slice avoids three of the four constraints you listed on #4101: compiled messages stay shareable across requests, because the config is constant for the lifetime of the process; prerendering has exactly one config to see, and freezing it into the HTML is the intent rather than a bug; and a per-region static build is unaffected. Only routes are left — the one you called most tractable. So if the general per-request version turns out to be too costly to support, the per-process slice is still worth having on its own, and it is all #4016 actually needs. Where the hook stops short of #4016. You named that gap here as "every candidate default has to be declared up front so its variants exist (#4016)". A locale-agnostic On the selection half, One cross-link for the perf constraint you raised: @ikz2018 posted a CPU profile on #4130 (Aug 20) where the request hook's |
🔗 Linked issue
📚 Description
This is an exploration and proof of concept on another approach to changing the module behavior by changing the config passed at setup, this would allow projects to introduce other semantics without introducing them to the module code directly.
This is based on what I describe in #4101 (comment), and could be a solution to some of the runtime config requests (a small subset of configs), which is a recurring feature request.
/cc @Vincentdevreede
Summary by CodeRabbit
nl-NL) as an available locale in the playground.