* 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.
* fix(pages-router): invoke Document.getInitialProps so docProps reach SSR (#1361)
`pages/_document.tsx` files that override `static async getInitialProps`
to inject extra props were rendered with an empty `this.props`, because
both the dev-server and the production pages response builder created
the Document element with `React.createElement(DocumentComponent)` and
never called the hook.
The Next.js async-modules e2e fixture
(`test/e2e/async-modules/pages/_document.jsx`) uses exactly that pattern
to surface a `docValue` from `await Document.getInitialProps(ctx)` plus
a top-level `await` constant. PR #1381 (issue #1361) fixed the class
construction crash, but the deploy-suite continued to fail
"csr async page modules" because `<div id="doc-value">` rendered empty.
This change adds a small `loadUserDocumentInitialProps` helper used by
both the Pages Router dev-server (`streamPageToResponse` shell and the
custom-error-page shell) and the production response builder
(`buildPagesShellHtml`). It invokes the override (if any) and spreads
the resolved props on the Document element, mirroring Next.js's
`render.tsx` `<Document {...htmlProps} {...docProps} />` call. The
helper short-circuits when the user did not override the base shim, so
the existing fast path keeps the same number of awaits.
Reference: vercel/next.js packages/next/src/server/render.tsx
(`loadDocumentInitialProps`, `documentElement`).
Tests:
- Adds a new pages-router production e2e case that builds a
`_document.tsx` with `static async getInitialProps` + top-level
`await`, plus a custom `404.tsx` that uses top-level `await`, and
asserts the resolved values reach the rendered HTML.
- Unit-tests `loadUserDocumentInitialProps` directly for the override,
no-override, and throwing-override cases.
Refs #1361
* fix(pages-router): address bonk review on Document.getInitialProps helper
Two changes from review feedback on PR #1633:
1. Cache the base getInitialProps reference at module level. The previous
helper did `await import("vinext/shims/document")` on every request to
compare identity — unnecessary per-request work and an extra await on
the fast path where the user has no override. Switched to a static
import + module-scoped constant so the identity check is a synchronous
reference compare. No-override callers now resolve with one less await.
2. Let user `getInitialProps` errors propagate. Next.js's `loadGetInitialProps`
does not catch — a throw surfaces as a 500 to the caller. The previous
version logged-and-swallowed the error and returned null, which
silently erased docProps on every failing render and made user bugs
harder to find. vinext now matches Next.js's contract.
Updated the corresponding unit test to assert the throw propagates rather
than that the helper resolves to null.
Refs #1361, #1633
* fix: make next/document default export a class
Next.js's `next/document` default export is `class Document extends
React.Component`. vinext's shim exported a function, so user
`_document.tsx` files that use the class-based form (the common Next.js
idiom: `class MyDocument extends Document`) ended up extending a
function. React refuses to call class constructors without `new`, which
caused SSR to throw "Class constructor MyDocument cannot be invoked
without 'new'" — surfacing in e2e as 500 / empty responses.
This is the actual root cause of the "async modules render empty pages"
report (issue #1361): the Next.js `test/e2e/async-modules` fixture's
`pages/_document.jsx` uses `class extends Document`, and once it crashes
the whole page renders empty. Top-level await itself propagates
correctly through Rolldown's static-import graph already.
Adds a class-based Document shim that mirrors Next.js's signature
(static `getInitialProps`, instance `render()`), plus production-build
e2e coverage for pages, `_app.tsx`, `getStaticProps`,
`getServerSideProps`, API routes, and class-based `_document.tsx`, all
using top-level `await`.
Closes#1361
Ports from Next.js: test/e2e/async-modules/index.test.ts
https://github.com/vercel/next.js/blob/canary/test/e2e/async-modules/index.test.ts
* fix(next/document): align Document base class with Next.js's typed contract
Addresses Bonk review feedback on PR #1381:
- Export DocumentContext / DocumentInitialProps types matching Next.js,
so subclasses that delegate via `await Document.getInitialProps(ctx)`
receive typed objects. The runtime path is still a stub (the Pages
Router renderPage/defaultGetInitialProps chain is not wired up), but
the signatures pin the contract.
- Declare the default Document class export in next-shims.d.ts so
strict-mode consumers don't see `any` for `import Document from
"next/document"`.
- Add a class-extends regression test in tests/document.test.ts — this
is the contract that broke in issue #1361 (the original Next.js
`pages/_document.jsx` uses `class extends Document`).
- Switch the class generic default from `unknown` to `{}` to match
Next.js's `class Document<P = {}>`.
- Clarify App Router fixture comment: the upstream Next.js async-modules
suite is Pages-Router-only, so this is adapted, not ported verbatim.
* 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
Cover Html, Head, Main, NextScript, and default Document with
renderToString assertions for attributes, children, placeholder
content, and correct nesting order.