Commit Graph

12 Commits

Author SHA1 Message Date
James Anderson 4ffd5bf225 fix(app-router): ensure streamed SSR body ends with </body></html> (#1532) (#1624)
* fix(app-router): ensure SSR stream ends with </body></html> suffix (#1532)

Trailing flight chunks and preinit scripts emitted by `rscEmbed.finalize()`
in the tick-buffered transform's `flush()` were being appended after React
Fizz's `</body></html>` closing tags, leaving the streamed document body
ending in `<script>...</script>` rather than a well-formed close.

Strip the first `</body></html>` we see in the buffered HTML chunks and
re-emit it at the very end of `flush()`, after the final RSC scripts.
Mirrors Next.js's `createMoveSuffixStream` behaviour
(packages/next/src/server/stream-utils/node-web-streams-helper.ts).

Ported from Next.js test:
test/e2e/app-dir/app/index.test.ts — "should ensure the </body></html>
suffix is at the end of the stream".

Fixes #1532.

* test(rsc-streaming): adjust progressive-delivery assertion for #1532

The 'delivers RSC chunks progressively' case still asserted the old order
where the navigation-runtime done signal trailed </html>. After #1532
suffix-moved </body></html> to the very end of the stream, the done
signal sits *between* the body content and the document close. Update
the assertion to match: done > last body content, and </html> > done.

* style: apply biome formatter to rsc-streaming.test.ts
2026-06-03 22:42:26 +01:00
James Anderson b5da92e717 fix(app-router): drop space before /> in streamed error meta tags (#1491) (#1651)
When notFound() or redirect() is called from inside a Suspense boundary
the error surfaces after the shell has already streamed, so the not-found
/ redirect intent is communicated to the client via inline <meta> tags
injected into the head section of the stream.

vinext was emitting these as `<meta name="robots" content="noindex" />`
(with a space before the self-closing slash), but Next.js serializes them
via React's HTML renderer, which produces `<meta ... content="..."/>`
without the space. The Next.js navigation tests assert on that exact
substring, so this mismatch caused ~5 deploy-suite failures.

Drop the extra space so the streamed meta tags match Next.js byte-for-byte,
and add ported regression tests against the suspense fixtures.

Ref: packages/next/src/server/app-render/make-get-server-inserted-html.tsx
Ref: test/e2e/app-dir/navigation/navigation.test.ts (SEO describe block)
2026-05-28 16:29:22 +01:00
Nathan Nguyen 0d8d3e3c60 refactor(router): quarantine navigation runtime topology (#1322)
* refactor(router): quarantine navigation runtime topology

* fix(router): restore manifest-backed interception runtime

App Router browser navigations lost first-hop intercepted route context after snapshot topology stopped acting as fallback proof. That made /feed to /photos/:id request the direct page payload instead of the declared modal payload.

The violated invariant was that route topology must come from the manifest, while snapshots only carry runtime proof. Resolve first-hop interception from the manifest, move RSC bootstrap streams onto the symbol-backed runtime, teach prerender extraction that script shape, and harden the runtime contract around route manifest and RSC data.

Coverage includes runtime seam tests, prerender extraction tests, planner/browser navigation tests, package rebuild, and the app-router intercepted routes E2E slice.

* fix(router): narrow prerender RSC script parsing

The prerender extractor accepted any script starting with the symbol-runtime bootstrap expression, then parsed it as though it had the exact RSC chunk-push prefix. That was harmless at the current head, but it made future runtime scripts a foot-gun.

Only the exact runtime chunk prefix and exact done marker are now accepted. A focused extractor test covers non-chunk runtime scripts that share the bootstrap expression prefix.

* fix(router): address navigation runtime review

* fix(router): resolve navigation runtime review threads
2026-05-20 10:29:13 +01:00
Nathan Nguyen 496a0247af fix(app-router): emit streamed redirect and not-found meta tags (#1261)
* fix(app-router): emit streamed redirect and not-found meta tags

Streaming App Router SSR currently returns a 200 once the shell commits, but redirect() or notFound() discovered inside a later Suspense boundary never emits the browser-visible meta tag that Next.js relies on. That leaves the control-flow digest only in the RSC payload until client hydration handles it.

The SSR onError path now captures digest-bearing render errors and a request-local flusher injects noindex or refresh meta tags through the existing server-inserted HTML stream hook. The app page pipeline also forwards basePath into SSR so streamed redirect meta tags match early redirect responses.

* fix(app-router): avoid double-prefixing basePath root redirects

Streamed redirect meta tags checked basePath against the full redirect location string. Already-prefixed root URLs with a query or hash, such as /docs?from=checkout, failed the pathname-shaped basePath check and were prefixed a second time.

Add a shared pathname-only basePath prefix helper and use it from both early redirect responses and streamed redirect metadata. The streamed path now splits query/hash before checking the pathname.
2026-05-16 19:07:27 +01:00
Nathan Nguyen ebeb044e01 fix(app-router): flush server-inserted HTML during SSR streaming (#926)
App Router SSR rendered useServerInsertedHTML callbacks only once before returning the HTML stream. CSS-in-JS registries registered at shell render were cleared before later Suspense chunks, so styles collected after the shell never reached streamed HTML.

Keep callback registrations alive for the stream lifetime and let the tick-buffered HTML transform request insertion HTML on each flush after the initial head injection. This matches Next.js per-chunk server inserted HTML transform and preserves cleanup on stream completion or cancellation.

Adds regression coverage for persistent callback rendering and a Next.js-ported Suspense CSS-in-JS streaming order case.
2026-04-28 19:35:10 +01:00
Stephen Zhou 14ff303cbd feat: csp support (#795)
* fix: support csp

* tweaks

* tweaks

* tweaks

* tweaks

* tweaks

* tweaks

* clean

* tweaks

* clean

* fix test

* tweaks

* fix lock

* fix test

* trigger ci

* tweaks

* tweaks
2026-04-09 09:14:13 +01:00
Steve Faulkner a6d2c3a09c Refactor App Router virtual entries into typed runtime modules (#616)
* refactor: move app router SSR/browser runtime into typed modules

* style: format typed app runtime modules

* refactor: address runtime entry review follow-ups
2026-03-20 23:03:27 -05: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
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
James Anderson 23a5cb2ceb refactor: delete app-dev-server.ts, point all callers at entries/ directly (#253 batch 3) (#379)
* refactor: delete app-dev-server.ts and update all references to entries/ directly

The file was already a pure re-export shim with no logic of its own.
Update every import/dynamic-import site to point straight at the
individual entry modules:

  entries/app-rsc-entry.ts    ← generateRscEntry, AppRouterConfig
  entries/app-ssr-entry.ts    ← generateSsrEntry
  entries/app-browser-entry.ts ← generateBrowserEntry

Sites updated:
  packages/vinext/src/index.ts
  tests/entry-templates.test.ts
  tests/app-router.test.ts
  tests/shims.test.ts

Also update stale comments in:
  server/middleware-codegen.ts, server/request-pipeline.ts,
  server/instrumentation.ts, shims/metadata.tsx,
  tests/rsc-streaming.test.ts, tests/nextjs-compat/rsc-context-lazy-stream.test.ts,
  examples/app-router-cloudflare/instrumentation*.ts

* docs: update app-dev-server.ts references in markdown files to entries/
2026-03-09 14:11:07 +00:00
Dane 98b243493f fix: resolve CodeQL security alerts (ReDoS, incomplete sanitization, bad code gen) (#87)
Refactor chained .replace() to single-pass tokenizers in pattern matching:
- matchConfigPattern in config-matchers.ts and index.ts
- escapeHeaderSource (new) replaces inline chained escaping in matchHeaders/applyHeaders
- matchMiddlewarePattern in prod server entry template
- matchPattern in middleware.ts

Also:
- escapeHeaderSource correctly handles :param(constraint) patterns
- Fix decodeURIComponent parity gap in index.ts matchConfigPattern
- Fix redundant dot in middleware.ts tokenizer char class
- Add lgtm suppression comments for false positives in test files
- Add unit tests for escapeHeaderSource
2026-02-26 00:18:46 -06:00
Steve Faulkner 12fea722b6 Initial public release of vinext 2026-02-24 09:29:39 -06:00