Skip to content

fix: avoid user package hydration mismatch#2981

Open
liangmiQwQ wants to merge 4 commits into
npmx-dev:mainfrom
liangmiQwQ:liang/codex/fix-1948-prehydrate
Open

fix: avoid user package hydration mismatch#2981
liangmiQwQ wants to merge 4 commits into
npmx-dev:mainfrom
liangmiQwQ:liang/codex/fix-1948-prehydrate

Conversation

@liangmiQwQ

@liangmiQwQ liangmiQwQ commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Close #1948, Close #2753

The /~username page rendered package search results on the server with the default search provider, while the client could hydrate with a different provider from localStorage. That could produce hydration mismatches and a visible provider swap for users who selected npm.

According to #1948 (comment), I end up choosing to disable SSR for this page. Package list fetching and other related works are all done in client now.

🤖 Generated with Codex

@vercel

vercel Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
npmx.dev Ready Ready Preview, Comment Jul 1, 2026 1:56pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs.npmx.dev Ignored Ignored Preview Jul 1, 2026 1:56pm
npmx-lunaria Ignored Ignored Jul 1, 2026 1:56pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Improved page loading behaviour so the spinner appears earlier while package data is still resolving.
    • Reduced hydration issues by mount-gating when the search provider setting is read, and by adjusting lazy loading behaviour to avoid unwanted SSR fetching.
  • Tests

    • Expanded end-to-end coverage for hydration and search-related pages, including a new route and a pre-filled search provider settings scenario.

Walkthrough

Changes address search-provider hydration mismatches by deferring the settings read until mount, disabling server-side fetching for user packages, broadening the username page loading state, and adding hydration e2e coverage for pages with a non-default search provider.

Changes

Search provider hydration fix

Layer / File(s) Summary
Mount-gated search provider setting
app/composables/useSettings.ts
useSearchProvider returns DEFAULT_SETTINGS.searchProvider before mount, then reads settings.value.searchProvider after mount; the setter still writes to stored settings.
Related loading state and SSR fetch adjustments
app/composables/npm/useUserPackages.ts, app/pages/~[username]/index.vue
useUserPackages sets server: false on its lazy async data options; the username page loading spinner now also renders for status === 'idle' when there are no packages and no error.
Hydration e2e coverage for search provider
test/e2e/hydration.spec.ts
Adds '/~qwerzl' to PAGES, introduces SEARCH_PROVIDER_PAGES, and adds a hydration test that seeds localStorage with npmx-settings.searchProvider = 'npm' and checks for no hydration errors.

Suggested reviewers: ghostdevv, 43081j

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and captures the main hydration-mismatch fix.
Description check ✅ Passed The description matches the PR and explains the SSR/client settings mismatch fix.
Linked Issues check ✅ Passed The changes address [#1948, #2753] by avoiding SSR reads of localStorage-backed settings and shifting package fetching client-side.
Out of Scope Changes check ✅ Passed All code and tests stay focused on the hydration-mismatch fix; no unrelated features were added.
✨ 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.

@codecov

codecov Bot commented Jul 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 50.00000% with 1 line in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
app/composables/useSettings.ts 50.00% 0 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

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.

🧹 Nitpick comments (2)
app/composables/npm/useUserPackages.ts (1)

113-113: 🚀 Performance & Scalability | 🔵 Trivial

Correct fix, but note the SSR/SEO trade-off.

Disabling server fetch here fully resolves the hydration mismatch, but it also means the ~[username] package list is no longer present in the server-rendered HTML — crawlers and no-JS clients will now see only a loading state until client-side fetch completes. This is consistent with the PR's stated approach, but as the linked issue notes, a longer-term fix of moving settings like searchProvider to cookies would let this stay SSR-rendered while still avoiding the mismatch.

🤖 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 `@app/composables/npm/useUserPackages.ts` at line 113, The hydration mismatch
fix in useUserPackages is to disable the server-side fetch for the package
search state by keeping the default emptySearchResponse fallback and setting the
request to client-only. Update the useUserPackages composable so the search data
for ~[username] is fetched only on the client, which prevents SSR from rendering
a stale package list against client state. Keep this change localized to the
package list fetch/config in useUserPackages, and note that it intentionally
trades SSR content for hydration stability.
app/composables/useSettings.ts (1)

199-212: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace the manual mount flag with useMounted()

useMounted() is a drop-in replacement for the current shallowRef + onMounted pair and keeps the composable a bit cleaner.

🤖 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 `@app/composables/useSettings.ts` around lines 199 - 212, `useSearchProvider`
currently uses a manual `shallowRef` plus `onMounted` to track mount state.
Replace that pattern with `useMounted()` in the same composable so
`searchProvider` continues to read `DEFAULT_SETTINGS.searchProvider` before
mount and `settings.value.searchProvider` after mount, while keeping the
existing computed getter/setter behavior unchanged.
🤖 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.

Nitpick comments:
In `@app/composables/npm/useUserPackages.ts`:
- Line 113: The hydration mismatch fix in useUserPackages is to disable the
server-side fetch for the package search state by keeping the default
emptySearchResponse fallback and setting the request to client-only. Update the
useUserPackages composable so the search data for ~[username] is fetched only on
the client, which prevents SSR from rendering a stale package list against
client state. Keep this change localized to the package list fetch/config in
useUserPackages, and note that it intentionally trades SSR content for hydration
stability.

In `@app/composables/useSettings.ts`:
- Around line 199-212: `useSearchProvider` currently uses a manual `shallowRef`
plus `onMounted` to track mount state. Replace that pattern with `useMounted()`
in the same composable so `searchProvider` continues to read
`DEFAULT_SETTINGS.searchProvider` before mount and
`settings.value.searchProvider` after mount, while keeping the existing computed
getter/setter behavior unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a9124468-b91c-45e1-976b-5b4d2758f874

📥 Commits

Reviewing files that changed from the base of the PR and between 1880374 and 0bba87f.

📒 Files selected for processing (4)
  • app/composables/npm/useUserPackages.ts
  • app/composables/useSettings.ts
  • app/pages/~[username]/index.vue
  • test/e2e/hydration.spec.ts

@gameroman gameroman left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could you address the Coderabbit's Nitpick comments please

@liangmiQwQ

Copy link
Copy Markdown
Contributor Author

I am dealing with it actively now :)

@coderabbitai coderabbitai Bot left a comment

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.

🧹 Nitpick comments (1)
app/composables/useSettings.ts (1)

198-207: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Good SSR-safe hydration fix; consider a brief comment.

Swapping the manual onMounted/shallowRef pattern for VueUse's useMounted() is correct and SSR-safe (it internally guards with getCurrentInstance() before registering the lifecycle hook), so the initial client render matches SSR output and avoids the hydration mismatch. Since the rationale isn't obvious from the code alone, a short comment would help future readers understand why mount-gating is needed here.

📝 Suggested comment
   const { settings } = useSettings()
+  // Fall back to the default value until mounted to avoid SSR/client hydration
+  // mismatches, since `settings` is sourced from localStorage (client-only).
   const isMounted = useMounted()

As per coding guidelines, "Add comments only to explain complex logic or non-obvious implementations."

🤖 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 `@app/composables/useSettings.ts` around lines 198 - 207, The SSR-safe mount
gating in useSearchProvider is correct, but the intent is not obvious from the
computed getter that switches between settings.value.searchProvider and
DEFAULT_SETTINGS.searchProvider based on useMounted(). Add a short inline
comment near the mount check in useSearchProvider explaining that the mounted
guard keeps the initial SSR/client render aligned and avoids hydration mismatch,
so future readers understand why the value is deferred until mount.

Source: Coding guidelines

🤖 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.

Nitpick comments:
In `@app/composables/useSettings.ts`:
- Around line 198-207: The SSR-safe mount gating in useSearchProvider is
correct, but the intent is not obvious from the computed getter that switches
between settings.value.searchProvider and DEFAULT_SETTINGS.searchProvider based
on useMounted(). Add a short inline comment near the mount check in
useSearchProvider explaining that the mounted guard keeps the initial SSR/client
render aligned and avoids hydration mismatch, so future readers understand why
the value is deferred until mount.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 86bf9d05-a996-4cd2-b8a7-8ee7923a9ad8

📥 Commits

Reviewing files that changed from the base of the PR and between 0bba87f and 2f8b665.

📒 Files selected for processing (1)
  • app/composables/useSettings.ts

@gameroman gameroman left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good 👍

Lets see what other maintainers think

Specifically the Coderabbit's comment about SEO

@gameroman gameroman added the needs review This PR is waiting for a review from a maintainer label Jul 1, 2026
@gameroman gameroman requested a review from a team July 1, 2026 14:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs review This PR is waiting for a review from a maintainer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

hydration error on org pages address settings related hydration issues using prehydrate

2 participants