Skip to content

Conversation

@huang-julien
Copy link
Member

📚 Description

resolves #33254

this PR adds an experimental feature to normalize page components names using the route name.

@bolt-new-by-stackblitz
Copy link

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

@huang-julien huang-julien marked this pull request as draft October 18, 2025 17:40
if (isSyncImport) {
metaRoute.component = `Object.assign(${pageImportName}, { __name: ${metaRoute.name} })`
} else {
metaRoute.component = `${metaRoute.component}.then((m) => Object.assign(m.default, { __name: ${metaRoute.name} }))`

Check warning

Code scanning / CodeQL

Improper code sanitization Medium

Code construction depends on an
improperly sanitized value
.

Copilot Autofix

AI 18 days ago

To prevent code injection vulnerabilities, we should escape unsafe characters in serialized values before interpolating them in generated JavaScript code. The best way is to follow the CodeQL-provided template: create a function that escapes potentially dangerous characters (<, >, /, \, and certain whitespace/unicode), and apply this to the output of JSON.stringify(value). Thus, edit the serializeRouteValue function (lines 516–519) in packages/nuxt/src/pages/utils.ts to use this sanitization.

  • Add a static map of risky character replacements (charMap) and an escaping function (escapeUnsafeChars) before/at the top of the relevant code region.
  • Within serializeRouteValue, after JSON.stringify, apply the escaping function to the string: return escapeUnsafeChars(JSON.stringify(value)).
  • You only need to change code inside the shown code region (lines 516–520 or nearby for helpers) in the file. Do not affect other code outside.
  • No new dependencies are necessary, as this uses pure JS.

Suggested changeset 1
packages/nuxt/src/pages/utils.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/packages/nuxt/src/pages/utils.ts b/packages/nuxt/src/pages/utils.ts
--- a/packages/nuxt/src/pages/utils.ts
+++ b/packages/nuxt/src/pages/utils.ts
@@ -513,9 +513,28 @@
   return routes
 }
 
+const charMap: Record<string, string> = {
+  '<': '\\u003C',
+  '>': '\\u003E',
+  '/': '\\u002F',
+  '\\': '\\\\',
+  '\b': '\\b',
+  '\f': '\\f',
+  '\n': '\\n',
+  '\r': '\\r',
+  '\t': '\\t',
+  '\0': '\\0',
+  '\u2028': '\\u2028',
+  '\u2029': '\\u2029'
+}
+
+function escapeUnsafeChars(str: string): string {
+  return str.replace(/[<>\b\f\n\r\t\0\u2028\u2029\\/]/g, x => charMap[x] || x)
+}
+
 function serializeRouteValue (value: any, skipSerialisation = false) {
   if (skipSerialisation || value === undefined) { return undefined }
-  return JSON.stringify(value)
+  return escapeUnsafeChars(JSON.stringify(value))
 }
 
 type NormalizedRoute = Partial<Record<Exclude<keyof NuxtPage, 'file'>, string>> & { component?: string }
EOF
@@ -513,9 +513,28 @@
return routes
}

const charMap: Record<string, string> = {
'<': '\\u003C',
'>': '\\u003E',
'/': '\\u002F',
'\\': '\\\\',
'\b': '\\b',
'\f': '\\f',
'\n': '\\n',
'\r': '\\r',
'\t': '\\t',
'\0': '\\0',
'\u2028': '\\u2028',
'\u2029': '\\u2029'
}

function escapeUnsafeChars(str: string): string {
return str.replace(/[<>\b\f\n\r\t\0\u2028\u2029\\/]/g, x => charMap[x] || x)
}

function serializeRouteValue (value: any, skipSerialisation = false) {
if (skipSerialisation || value === undefined) { return undefined }
return JSON.stringify(value)
return escapeUnsafeChars(JSON.stringify(value))
}

type NormalizedRoute = Partial<Record<Exclude<keyof NuxtPage, 'file'>, string>> & { component?: string }
Copilot is powered by AI and may make mistakes. Always verify output.
@pkg-pr-new
Copy link

pkg-pr-new bot commented Oct 18, 2025

Open in StackBlitz

@nuxt/kit

npm i https://pkg.pr.new/@nuxt/kit@33513

nuxt

npm i https://pkg.pr.new/nuxt@33513

@nuxt/rspack-builder

npm i https://pkg.pr.new/@nuxt/rspack-builder@33513

@nuxt/schema

npm i https://pkg.pr.new/@nuxt/schema@33513

@nuxt/vite-builder

npm i https://pkg.pr.new/@nuxt/vite-builder@33513

@nuxt/webpack-builder

npm i https://pkg.pr.new/@nuxt/webpack-builder@33513

commit: ef39e42

@codspeed-hq
Copy link

codspeed-hq bot commented Oct 18, 2025

CodSpeed Performance Report

Merging #33513 will not alter performance

Comparing feat/33254 (ef39e42) with main (934dc00)

Summary

✅ 10 untouched

@huang-julien huang-julien marked this pull request as ready for review October 20, 2025 19:13
@coderabbitai
Copy link

coderabbitai bot commented Oct 20, 2025

Walkthrough

This pull request introduces a new experimental feature normalizePageNames that ensures page component names match their route names. The implementation adds logic to the pages utility module to conditionally attach a __name property to page components when the feature flag is enabled. Configuration types and defaults are added to the schema package. Supporting tests are included to verify both synchronous and asynchronous import scenarios.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

The changes span multiple packages with consistent patterns: a new experimental configuration option with schema definitions, conditional logic to augment component metadata, and dedicated tests for the new feature. The implementation logic is relatively straightforward (wrapping imports with Object.assign or promise chains), but review requires verification that the conditional logic correctly handles both sync and async import paths, proper integration with the experimental flag, and validation that test coverage adequately reflects the new behaviour. The heterogeneity is moderate—whilst most changes follow the same pattern (adding the flag and implementation), the different import method handling adds some complexity.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title "feat(nuxt): experimental page component name normalisation" clearly describes the main change in the pull request — the introduction of an experimental feature for normalizing page component names using route names. It follows conventional commit conventions, is concise and specific, and directly corresponds to the core functionality being added across the modified files (schema updates, utility logic, and test coverage).
Linked Issues Check ✅ Passed The linked issue #33254 requests automatic use of route names as page component names to enable keep-alive functionality with name-based references. The implementation satisfies this requirement by introducing a new experimental flag normalizePageNames in the schema [packages/schema/src/config/experimental.ts and packages/schema/src/types/schema.ts], implementing logic in utils.ts to attach __name properties to page components via Object.assign (for sync imports) and then() chaining (for async imports) when the flag is enabled, and providing comprehensive test coverage [packages/nuxt/test/pages.test.ts] validating both sync and async import scenarios.
Out of Scope Changes Check ✅ Passed All modifications are directly aligned with implementing the experimental page component name normalisation feature. Changes include the core logic in packages/nuxt/src/pages/utils.ts to attach component names, schema configuration in packages/schema/src/config/experimental.ts and packages/schema/src/types/schema.ts to define the experimental flag, and test files in packages/nuxt/test/ to validate the functionality. No changes appear to be tangential or unrelated to the stated objectives.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/33254

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 934dc00 and ef39e42.

📒 Files selected for processing (5)
  • packages/nuxt/src/pages/utils.ts (3 hunks)
  • packages/nuxt/test/page-metadata.test.ts (1 hunks)
  • packages/nuxt/test/pages.test.ts (2 hunks)
  • packages/schema/src/config/experimental.ts (1 hunks)
  • packages/schema/src/types/schema.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Follow standard TypeScript conventions and best practices

Files:

  • packages/schema/src/types/schema.ts
  • packages/nuxt/src/pages/utils.ts
  • packages/schema/src/config/experimental.ts
  • packages/nuxt/test/pages.test.ts
  • packages/nuxt/test/page-metadata.test.ts
**/*.{test,spec}.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Write unit tests for core functionality using vitest

Files:

  • packages/nuxt/test/pages.test.ts
  • packages/nuxt/test/page-metadata.test.ts
🧠 Learnings (1)
📚 Learning: 2024-12-12T12:36:34.871Z
Learnt from: huang-julien
PR: nuxt/nuxt#29366
File: packages/nuxt/src/app/components/nuxt-root.vue:16-19
Timestamp: 2024-12-12T12:36:34.871Z
Learning: In `packages/nuxt/src/app/components/nuxt-root.vue`, when optimizing bundle size by conditionally importing components based on route metadata, prefer using inline conditional imports like:

```js
const IsolatedPage = route?.meta?.isolate ? defineAsyncComponent(() => import('#build/isolated-page.mjs')) : null
```

instead of wrapping the import in a computed property or importing the component unconditionally.

Applied to files:

  • packages/nuxt/src/pages/utils.ts
🧬 Code graph analysis (2)
packages/nuxt/src/pages/utils.ts (2)
packages/nuxt/src/app/nuxt.ts (1)
  • nuxt (272-272)
packages/kit/src/index.ts (1)
  • useNuxt (28-28)
packages/nuxt/test/pages.test.ts (3)
packages/kit/src/context.ts (1)
  • useNuxt (29-36)
packages/schema/src/types/hooks.ts (1)
  • NuxtPage (28-51)
packages/nuxt/src/pages/utils.ts (1)
  • normalizeRoutes (528-658)
🪛 GitHub Check: CodeQL
packages/nuxt/src/pages/utils.ts

[warning] 598-598: Improper code sanitization
Code construction depends on an improperly sanitized value.

🔇 Additional comments (9)
packages/schema/src/types/schema.ts (1)

1301-1304: LGTM! Clear type definition for the new experimental feature.

The new normalizePageNames option is well-documented and follows the established pattern of normalizeComponentNames.

packages/nuxt/test/page-metadata.test.ts (1)

14-28: LGTM! Proper test isolation.

The mock correctly ensures that existing page metadata tests continue to run with normalizePageNames disabled, maintaining test isolation and backward compatibility.

packages/schema/src/config/experimental.ts (1)

171-171: LGTM! Appropriate default for experimental feature.

The feature is opt-in by default, which is the right approach for an experimental feature that changes component naming behaviour.

packages/nuxt/src/pages/utils.ts (3)

529-529: LGTM! Accessing Nuxt context.

The useNuxt() call is necessary to access the experimental configuration flag later in the function.


577-578: LGTM! Cleaner code with derived variable.

Extracting isSyncImport improves readability and allows reuse in the normalizePageNames logic below.


594-600: No security concerns found—CodeQL warning is a false positive.

The implementation is secure. The genSafeVariableName function from knitwork v1.2.0 converts arbitrary strings to safe JavaScript identifiers, properly handling special characters and reserved words. More importantly, route.name is always safely escaped via JSON.stringify before being interpolated into metaRoute.name at line 581, and then further interpolated into the component expression at line 598. The resulting code like Object.assign(index, { __name: indexMeta?.name ?? "index" }) is valid and free from injection risks because both the variable names and string values are appropriately constrained by these safe sources.

packages/nuxt/test/pages.test.ts (3)

8-24: LGTM! Consistent test setup.

The mock setup properly isolates existing tests from the new feature by defaulting normalizePageNames to false.


97-123: LGTM! Comprehensive test for sync imports.

The test correctly:

  • Enables the normalizePageNames feature via mock
  • Creates a page with _sync: true to trigger synchronous import behaviour
  • Verifies that Object.assign is used to attach __name

125-149: LGTM! Comprehensive test for async imports.

The test correctly:

  • Enables the normalizePageNames feature via mock
  • Creates a page without _sync to trigger asynchronous import behaviour
  • Verifies that .then() is chained to attach __name after import resolution

Together with the sync import test, this provides complete coverage of both code paths.


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.

: pageImport,
}

if (nuxt.options.experimental.normalizePageNames) {
Copy link
Member

Choose a reason for hiding this comment

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

could we maybe split this into normalizeComponent and normalizeComponentWithName functions, so we only assign it once, rather than manipulating an existing string?

Copy link
Member Author

Choose a reason for hiding this comment

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

Do you mean a normalizeComponentName at runtime ?

Copy link
Member

Choose a reason for hiding this comment

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

no. I mean, at the moment it's created a few lines above and then changed here

we can instead create it just once, which will tidy up the logic

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Use route name as page component's name as default

3 participants