Skip to content

Comments

refactor: move from NuxtApp augment to NuxtPayload#240

Merged
huang-julien merged 1 commit intomainfrom
refactor/nuxtapptopayload
Feb 23, 2026
Merged

refactor: move from NuxtApp augment to NuxtPayload#240
huang-julien merged 1 commit intomainfrom
refactor/nuxtapptopayload

Conversation

@huang-julien
Copy link
Member

🔗 Linked issue

❓ Type of change

  • 📖 Documentation (updates to the documentation or readme)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • 👌 Enhancement (improving an existing functionality)
  • ✨ New feature (a non-breaking change that adds functionality)
  • 🧹 Chore (updates to the build process or auxiliary tools and libraries)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

📚 Description

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 23, 2026

Open in StackBlitz

npm i https://pkg.pr.new/nuxt/hints/@nuxt/hints@240

commit: 5645a87

@huang-julien huang-julien merged commit e4568d9 into main Feb 23, 2026
6 of 7 checks passed
@huang-julien huang-julien deleted the refactor/nuxtapptopayload branch February 23, 2026 19:03
@coderabbitai
Copy link

coderabbitai bot commented Feb 23, 2026

📝 Walkthrough

Walkthrough

This pull request refactors the storage location of hints and state data structures within the Nuxt application. Hints previously stored directly on the NuxtApp instance (__hints, __hints_tpc) and as a root-level payload property (_lazyHydrationState) are now consolidated under a nested payload.__hints container. The changes include reorganizing hydration mismatch tracking, lazy hydration state, third-party script tracking, and web vitals data. Type definitions are updated to introduce a new NuxtPayload interface with __hints properties, remove the __hints_tpc property from NuxtApp, and add new tracer-related properties. Tests are updated to reference the new storage locations.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main refactoring: moving hints data structure from NuxtApp augmentation to NuxtPayload, which matches the primary changes across all modified files.
Description check ✅ Passed The description, while minimal and containing only templates, does not contradict the changeset. The intent stated in PR objectives aligns with the refactoring, though specific details are not provided.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/nuxtapptopayload

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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

@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 (2)
src/runtime/web-vitals/plugin.client.ts (1)

40-42: Consider caching nuxtApp.payload.__hints.webvitals in a local variable.

The third-party-scripts plugin caches the ref locally (const scripts = nuxtApp.payload.__hints.thirdPartyScripts). The same pattern would reduce repetition here:

♻️ Suggested refactor
     nuxtApp.payload.__hints = defu(nuxtApp.payload.__hints, {
       webvitals: {
         lcp: ref([]),
         inp: ref([]),
         cls: ref([]),
       },
     })
+    const webvitals = nuxtApp.payload.__hints.webvitals
 
     nuxtApp.hook('hints:webvitals:sync', (webvitals) => {
-      webvitals.lcp.value = [...nuxtApp.payload.__hints.webvitals.lcp.value]
-      webvitals.inp.value = [...nuxtApp.payload.__hints.webvitals.inp.value]
-      webvitals.cls.value = [...nuxtApp.payload.__hints.webvitals.cls.value]
+      webvitals.lcp.value = [...nuxtApp.payload.__hints.webvitals.lcp.value] 
+      webvitals.inp.value = [...nuxtApp.payload.__hints.webvitals.inp.value]
+      webvitals.cls.value = [...nuxtApp.payload.__hints.webvitals.cls.value]
     })

Note: The hook callback parameter shadows the outer webvitals name, so the sync hook body must still use the full path (or rename the parameter). But the metric push sites can use the local:

-        nuxtApp.payload.__hints.webvitals.inp.value.push(metric)
+        webvitals.inp.value.push(metric)

(Apply similarly for lcp and cls pushes.)

Also applies to: 54-54, 68-68, 133-133

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

In `@src/runtime/web-vitals/plugin.client.ts` around lines 40 - 42, Cache
nuxtApp.payload.__hints.webvitals into a local constant (e.g. const
webvitalsHints = nuxtApp.payload.__hints.webvitals) and use
webvitalsHints.lcp.value, webvitalsHints.inp.value and webvitalsHints.cls.value
when assigning to webvitals.lcp.value / webvitals.inp.value /
webvitals.cls.value to avoid repeated deep access; be careful that any hook
callback parameter named webvitals may shadow the outer variable so either
rename the parameter or keep full path inside the hook while using the cached
webvitalsHints for the metric push sites (apply same change for the other
occurrences mentioned).
src/runtime/types.d.ts (1)

41-56: __hints and its sub-properties are typed as non-optional, but initialized piecemeal by separate plugins.

Each plugin initializes only its own slice via defu (e.g., the hydration plugin only sets hydration: []). Between plugin setup calls, properties like webvitals or thirdPartyScripts won't exist yet, even though the type declares them as required. If plugin execution order changes or a consumer reads payload.__hints before all plugins run, this will produce a runtime error despite TypeScript being satisfied.

Consider either:

  1. Making each sub-property optional (hydration?: ..., webvitals?: ..., etc.), or
  2. Adding a single centralized initialization plugin (with high priority) that sets up the full __hints object once.
Option 1: Make sub-properties optional
   interface NuxtPayload {
     __hints: {
       lazyHydrationState?: LazyHydrationState
-      hydration: LocalHydrationMismatch[]
-      lazyComponents: DirectImportInfo[]
-      webvitals: {
+      hydration?: LocalHydrationMismatch[]
+      lazyComponents?: DirectImportInfo[]
+      webvitals?: {
         lcp: Ref<LCPMetricWithAttribution[]>
         inp: Ref<INPMetricWithAttribution[]>
         cls: Ref<CLSMetricWithAttribution[]>
       }
-      thirdPartyScripts: Ref<{
+      thirdPartyScripts?: Ref<{
         element: HTMLScriptElement
         loaded: boolean
       }[]>
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/types.d.ts` around lines 41 - 56, The NuxtPayload.__hints object
declares required sub-properties that are populated piecemeal by separate
plugins, risking runtime undefined access; update the NuxtPayload interface so
each sub-property under __hints (e.g., lazyHydrationState, hydration,
lazyComponents, webvitals, thirdPartyScripts) is optional (use ? types) OR
alternatively implement a single high-priority initialization plugin that
creates a complete __hints object before other plugins run; modify the
declaration for NuxtPayload::__hints or add the init plugin to ensure all
consumers can safely access those fields.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/runtime/lazy-load/composables.test.ts`:
- Around line 92-96: The test reads nuxtApp.payload.__hints?.lazyHydrationState
which types state as possibly undefined, causing TypeScript errors when
accessing properties even though setup!() guarantees initialization; remove the
optional chaining so you read nuxtApp.payload.__hints.lazyHydrationState (and
similarly at the other occurrences around the reads at lines referencing state)
so state is non-nullable, then keep the runtime expect(state).toBeDefined()
assertions for safety — update the accesses in this file (the variable named
state and the expression nuxtApp.payload.__hints?.lazyHydrationState) to use the
non-optional form.

---

Nitpick comments:
In `@src/runtime/types.d.ts`:
- Around line 41-56: The NuxtPayload.__hints object declares required
sub-properties that are populated piecemeal by separate plugins, risking runtime
undefined access; update the NuxtPayload interface so each sub-property under
__hints (e.g., lazyHydrationState, hydration, lazyComponents, webvitals,
thirdPartyScripts) is optional (use ? types) OR alternatively implement a single
high-priority initialization plugin that creates a complete __hints object
before other plugins run; modify the declaration for NuxtPayload::__hints or add
the init plugin to ensure all consumers can safely access those fields.

In `@src/runtime/web-vitals/plugin.client.ts`:
- Around line 40-42: Cache nuxtApp.payload.__hints.webvitals into a local
constant (e.g. const webvitalsHints = nuxtApp.payload.__hints.webvitals) and use
webvitalsHints.lcp.value, webvitalsHints.inp.value and webvitalsHints.cls.value
when assigning to webvitals.lcp.value / webvitals.inp.value /
webvitals.cls.value to avoid repeated deep access; be careful that any hook
callback parameter named webvitals may shadow the outer variable so either
rename the parameter or keep full path inside the hook while using the cached
webvitalsHints for the metric push sites (apply same change for the other
occurrences mentioned).

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c3c969f and 5645a87.

📒 Files selected for processing (10)
  • client/app/composables/host.ts
  • client/app/plugins/hydration.ts
  • src/runtime/hydration/composables.ts
  • src/runtime/hydration/plugin.client.ts
  • src/runtime/lazy-load/composables.ts
  • src/runtime/lazy-load/plugin.client.ts
  • src/runtime/third-party-scripts/plugin.client.ts
  • src/runtime/types.d.ts
  • src/runtime/web-vitals/plugin.client.ts
  • test/runtime/lazy-load/composables.test.ts

Comment on lines 92 to 96
const nuxtApp = useNuxtApp()
const state = nuxtApp.payload._lazyHydrationState
const state = nuxtApp.payload.__hints?.lazyHydrationState
expect(state).toBeDefined()
expect(state.directImports.get('Child')).toEqual(imports[0])
})
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

state is typed T | undefined after ?. — unguarded property accesses may cause TypeScript errors.

nuxtApp.payload.__hints?.lazyHydrationState infers LazyHydrationState | undefined. expect(state).toBeDefined() guards the runtime path but TypeScript's control-flow analysis does not narrow through Vitest assertions, so state.directImports (line 95) is a potential strict-mode type error. The same pattern repeats at lines 124 and 145 (line 145 lacks even a toBeDefined() guard before property access at 147–149).

Since setup!() is called before each read, __hints.lazyHydrationState is guaranteed to be initialized. Drop the optional chain to get a non-nullable type:

🛡️ Proposed fix (same pattern applies to lines 124 and 145)
-     const state = nuxtApp.payload.__hints?.lazyHydrationState
+     const state = nuxtApp.payload.__hints!.lazyHydrationState
      expect(state).toBeDefined()
      expect(state.directImports.get('Child')).toEqual(imports[0])
📝 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.

Suggested change
const nuxtApp = useNuxtApp()
const state = nuxtApp.payload._lazyHydrationState
const state = nuxtApp.payload.__hints?.lazyHydrationState
expect(state).toBeDefined()
expect(state.directImports.get('Child')).toEqual(imports[0])
})
const nuxtApp = useNuxtApp()
const state = nuxtApp.payload.__hints!.lazyHydrationState
expect(state).toBeDefined()
expect(state.directImports.get('Child')).toEqual(imports[0])
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/runtime/lazy-load/composables.test.ts` around lines 92 - 96, The test
reads nuxtApp.payload.__hints?.lazyHydrationState which types state as possibly
undefined, causing TypeScript errors when accessing properties even though
setup!() guarantees initialization; remove the optional chaining so you read
nuxtApp.payload.__hints.lazyHydrationState (and similarly at the other
occurrences around the reads at lines referencing state) so state is
non-nullable, then keep the runtime expect(state).toBeDefined() assertions for
safety — update the accesses in this file (the variable named state and the
expression nuxtApp.payload.__hints?.lazyHydrationState) to use the non-optional
form.

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