Skip to content

fix: let useConfig() inside UI components take precedence upon client-side navigation (vikejs/vike#3525) - #230

Merged
brillout merged 1 commit into
mainfrom
claude/sharp-goodall-t1gcwp
Sep 25, 2026
Merged

brillout merged 1 commit into
mainfrom
claude/sharp-goodall-t1gcwp

Conversation

@brillout

@brillout brillout commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

Fixes the bug reported at vikejs/vike#3525.

Bug

Upon client-side navigation, a title set by useConfig() (or <Config>) inside a UI component briefly showed up, and then got overwritten by the title set by +data() (via useConfig()) or by +title.

Cause: useConfig-client.ts checked '_headAlreadySet' in pageContext instead of the flag's value. Since onRenderClient() always sets pageContext._headAlreadySet (to false upon client-side navigation), components always wrote to document.title directly while rendering. Then, after await renderPromise, onRenderClient() applied pageContext._configViaHook (values from +data()) or pageContext.config.title, overwriting the component's value.

Fix

  • useConfig-client.ts checks !pageContext._headAlreadySet (like useConfig-server.ts and vike-vue do). While the page renders during client-side navigation, component values are now stored in pageContext._configViaHook and applied by onRenderClient(). They're stored after the values set by Vike hooks, so they take precedence, which matches SSR:
    1. useConfig() inside UI components
    2. useConfig() inside Vike hooks
    3. page settings
    4. global settings
  • The client-side setPageContextConfigViaHook() skips undefined values, like the server-side version does. Otherwise config({ title: maybeUndefined }) in a component would now wipe the title set by +data().

Second fix: pageContext.config.stream mutated between requests

The new test surfaced an unrelated bug that the preview test +react.{server.client}.js had only passed by luck. resolveStreamSetting() called stream?.reverse(), which reverses the shared pageContext.config.stream array in place. In production, the precedence of +stream values therefore flips on every request: the landing page of examples/full (stream: false, while the global config has stream: true) was streamed on every other request. Fixed by copying before reversing, as getHeadSetting() already does. This is a separate commit.

Tests (examples/full)

  • /images: <Image> now sets <Config title>, and a new +data() sets a title via useConfig(). Tests check that the component's title wins in the HTML, after hydration, and after client-side navigation. They also check that the title doesn't leak to the next page.
  • +react.{server.client}.js now runs twice in a row, so it deterministically catches the stream mutation.
  • Without the fixes, useConfig() in UI components upon client-side navigation fails with expected 'Images' to equal 'Image created by Romuald Brillout'. +react.{server.client}.js (request 1) fails in preview.
  • Locally: all e2e tests pass (all examples, dev and preview), as do test:types, test:units and format:check.

Related PRs

🤖 Generated with Claude Code

https://claude.ai/code/session_016vKLUPaAR8j8CqQfKQEa6M

@brillout
brillout force-pushed the claude/sharp-goodall-t1gcwp branch from ba10f6b to c534ab9 Compare September 25, 2026 09:03
@brillout
brillout merged commit e339dbc into main Sep 25, 2026
6 of 7 checks passed
@brillout
brillout deleted the claude/sharp-goodall-t1gcwp branch September 25, 2026 09:03
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