Commit Graph

9 Commits

Author SHA1 Message Date
James Anderson a98c8ae595 fix(shims/head): keep charset/viewport defaults on client hydration (#1713)
* 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.
2026-06-01 12:20:19 +01:00
James Anderson ffc8e68c4a fix(shims/head): match Next.js charset/viewport order and merge _document.getInitialProps head (#1677)
* 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.
2026-06-01 11:06:28 +01:00
James Anderson 97c11cdc37 fix(pages-router): rename data-vinext-head to data-next-head (#1559)
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.
2026-05-22 15:54:59 +01:00
MK (fengmk2) 9d5a2e9a77 chore(deps): bump vite-plus to 0.1.21 (#1190)
- 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
2026-05-14 09:14:31 +01:00
Nathan Nguyen ee261e33eb fix(head): preserve dangerous html during client sync (#1180)
* 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>
2026-05-13 11:34:32 +01:00
Stephen Zhou c17d6941be chore: migrate to vite plus (#535)
* 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
2026-03-15 10:50:13 +00:00
Nathan Nguyen f3994c655c Fix next/head key deduplication (#518)
* Fix next/head key deduplication

* Fix multi-child next/head deduplication

* Align next/head key normalization with Next.js
2026-03-13 10:11:44 +00:00
James Anderson 764a496ce7 add oxfmt formatter (#380)
* 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
2026-03-09 14:56:14 +00:00
Steve Faulkner 12fea722b6 Initial public release of vinext 2026-02-24 09:29:39 -06:00