* fix(shims/head): keep charset/viewport defaults on client hydration
The Pages Router next/head client sync (syncClientHead) removed every
[data-next-head] element and rebuilt the projection from only the user
<Head> children, omitting defaultHead() (charset + viewport). After the
first <Head> mounted on the client, the server-rendered charset and
viewport meta tags were stripped and never re-added.
PR #1677 seeded defaultHead() into the SSR collector (getSSRHeadHTML)
but not the client path. Next.js's reduceComponents() always concatenates
defaultHead() on both server and client, so the defaults must survive
hydration here too.
Seed defaultHead() into the client reduce, mirroring the SSR path. Users
can still override via key="charset" / key="viewport" through the same
dedupe pipeline.
Adds a regression test exercising the client projection with a minimal
DOM double (the repo has no jsdom).
* fix(shims/head): reconcile client head in place to preserve SSR order
The client sync still wiped every [data-next-head] node and re-appended
the projection, which moved the SSR-emitted charset/viewport/title tags to
the end of <head> (after the modulepreload/script links) and caused DOM
churn/flicker on every <Head> update.
Mirror Next.js's head-manager.ts updateElements(): diff the desired tags
against existing ones via isEqualNode(), leave matches untouched in their
original DOM position, append only genuinely new tags, remove stale ones,
and prepend a newly-created <meta charset> so it stays first.
Extends the regression tests with a richer DOM double asserting node reuse,
order preservation, insertion, removal, and charset-first behavior.
* test(shims/head): client-side override coverage; document divergences
Address non-blocking review notes from /bigbonk:
- Add a client-side test asserting a user key="viewport" overrides the
default viewport on _syncClientHead (mirrors the SSR override test),
locking in the dedupe-precedence parity claim.
- Document the deliberate charset prepend if/else divergence from Next.js's
head-manager.ts (which prepends then unconditionally appendChilds).
- Document the <title> reconciliation divergence (Next.js sets document.title
directly; we reconcile title like any tag — cosmetic only).
- Reconcile issue references to #1569 / #1677 in the client describe block.
* fix(shims/head): match Next.js charset/viewport order and merge _document.getInitialProps head (#1569)
Pages Router head emission did not match Next.js's canonical order
(`<meta charset>` then `<meta viewport>` then user tags, all with
`data-next-head=""`) and ignored head tags returned from a user
`_document.getInitialProps()`.
- `next/head` shim now seeds `defaultHead()` (charset + viewport) into
the SSR collector before user tags, mirroring `defaultHead()` in
`next/src/shared/lib/head.tsx`. They flow through the same dedupe
pipeline so users can still override via `key="charset"` /
`key="viewport"`.
- Serialised attribute names follow HTML conventions
(`charSet` -> `charset`, `httpEquiv` -> `http-equiv`, etc.), matching
Next.js's `test/e2e/next-head/index.test.ts` exact-string assertion.
- `next/document` shim no longer hardcodes charset/viewport in `<Head>`
(those defaults now flow from `next/head`); fallback HTML shells in
`dev-server.ts` and `pages-page-response.ts` likewise drop the
duplicates.
- New `setDocumentInitialHead()` accessor and ALS-backed state lets the
SSR pipeline forward head tags returned by user
`_document.getInitialProps()` into the same dedupe pipeline as
`next/head` children. Both the dev server and the prod
`renderPagesPageResponse` invoke `_document.getInitialProps()` when
present and pass the result through.
Adds focused regression coverage in `tests/head.test.ts` for ordering,
default attribute serialisation, key-based overrides, and the
`_document.getInitialProps()` merge path.
Refs #1569.
* fix(pages-router): address review feedback on _document head merge
- Extract shared `callDocumentGetInitialProps` helper under
`server/document-initial-head.ts` so both `dev-server.ts` and
`pages-page-response.ts` go through the same code path. The previous
duplicated try/catch blocks drifted independently and were the kind of
runtime-behavior that AGENTS.md says should live in a helper.
- Skip the call when `DocumentComponent.getInitialProps` is still the
unmodified default inherited from vinext's `next/document` shim.
Comparing against the captured base reference (`DEFAULT_GET_INITIAL_PROPS`)
distinguishes a user override from the default — extending `Document`
without overriding the method inherits the same static function, so
invoking it on every render is wasted work.
- Use `jsxAttrToHtml` on the client-side `_applyHeadPropsToElement` path
so `httpEquiv` is set as `http-equiv` rather than `httpequiv`
(lowercased by `setAttribute`). Matches the SSR output and prevents a
hydration mismatch when SSR emits the hyphenated form.
- Update stale `tests/document.test.ts` Head assertions: the shim's
`<Head>` no longer hardcodes charset/viewport — those flow through
`next/head`'s `defaultHead()` so the dedupe pipeline handles them.
Pages Router <Head> now emits the canonical `data-next-head=""`
attribute matching Next.js, rather than the vinext-specific
`data-vinext-head="true"`. This unblocks `_document` head lookups
in the deploy suite.
- Bump vite, vite-plus, vitest catalog entries to 0.1.21
- Refresh pnpm-lock.yaml
- Regenerate AGENTS.md via vp config postinstall
- Drop unused imports (PHASE_PRODUCTION_BUILD, patternToNextFormat) flagged by upgraded lint rules
- Move react-hooks/rules-of-hooks suppression in tests/e2e/fixtures.ts to the use(errors) call site
- Add react/no-children-prop and react/no-danger-with-children suppressions on the intentional precedence test in tests/head.test.ts
* fix(head): preserve dangerous html during client sync
Client-managed next/head tags currently drop dangerouslySetInnerHTML when hydration or navigation rebuilds vinext-owned head nodes. That diverges from Next.js and can leave SSR-injected script, style, or noscript content empty after the client projection runs.
The client path treated raw HTML as unsafe even though the SSR path already accepts the developer opt-in. Reuse one typed prop application helper for client head nodes and set innerHTML from dangerouslySetInnerHTML with the same precedence as Next.js.
Add regression coverage for client raw HTML application, empty __html fallback, and precedence over children.
* fix(head): handle array children and add children-only test coverage
Address review feedback:
- Handle Array.isArray(children) in _applyHeadPropsToElement by joining
array elements, matching Next.js reactElementToDOM behavior
- Add test for children-only path (no dangerouslySetInnerHTML) to ensure
the extracted helper didn't regress the common case
- Add test for array children joining
* fix(head): resolve SSR/client parity gap for empty dangerouslySetInnerHTML
Address second review: dangerouslySetInnerHTML: { __html: '' } was
handled inconsistently between SSR and client. The SSR path used a
truthiness check (if (html)) which skipped empty strings, falling
through to children. The client path checked the prop object (truthy),
so empty __html correctly took precedence.
Fix: restructure headChildToHTML to check dangerouslySetInnerHTML before
children using != null, making both paths consistent with Next.js
semantics where dangerouslySetInnerHTML always wins when present.
* fix(head): unify SSR/client content handling via getDangerouslySetInnerHTML
Address third review round:
- SSR: add Array.isArray(children) handling to headChildToHTML, matching
the client path for cases like createElement('title', null, 'A', 'B')
- Client: replace truthiness check with getDangerouslySetInnerHTML as
the gatekeeper (matching SSR pattern). This fixes silent content wipes
for malformed props like dangerouslySetInnerHTML: 'string' or
dangerouslySetInnerHTML: {} (missing __html key)
- Both paths now use the exact same entry point and condition shape:
getDangerouslySetInnerHTML() != null
- Update tests: malformed dangerouslySetInnerHTML: {} now correctly
falls through to children on both SSR and client
---------
Co-authored-by: James <james@eli.cx>
* chore: migrate to vite plus
* Disable typeAware and typeCheck
* Update CI
* Fix CI
* Fix test
* Clean
* Run test with vp
* Try revert
* react: false In test
* Fix test
* Revert "Try revert"
This reverts commit 009da10473.
* Update
* Update
* Try revert ci changes
* revert
* Run vp migrate
* Disable typeAware and typeCheck for now
* Better resolve for test
* Use vp dev instead of vite
* Update expect
* Fix NormalizeManifestModuleId
* Try increase timeout
* Update to use vp
* Try new check
* Bring back npx vp
* Migrate CI
* Make next-intl resolvable
* Update
* Update
* Update
* add oxfmt formatter: config, scripts, CI, editor setup, docs
* rebuild lockfile
* fix: add Format to required checks list, remove dead ignore pattern
* run fmt
* add format to agents.md again