Skip to content

Move the Flight rewriter into the Next.js module - #1381

Open
jwrosewell wants to merge 39 commits into
feature/39-source-modulesfrom
feature/40-rsc-flight
Open

jwrosewell wants to merge 39 commits into
feature/39-source-modulesfrom
feature/40-rsc-flight

Conversation

@jwrosewell

@jwrosewell jwrosewell commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Changes

Area Change
crates/framework/nextjs 3 files, 696 lines added, 0 removed
crates/trusted-server-core/src 8 files, 309 lines added, 487 removed
docs/superpowers 1 file, 109 lines added, 0 removed
docs/guide 3 files, 37 lines added, 18 removed
crates/trusted-server-modules/src 1 file, 12 lines added, 2 removed
trusted-server.example.toml 1 file, 11 lines added, 2 removed
CHANGELOG.md 1 file, 3 lines added, 1 removed
crates/trusted-server-core/README.md 1 file, 2 lines added, 3 removed

Closes

Closes #1340

Spec

Spec: rsc-flight-middleware

Test plan

CI on this pull request at 61de267 passed all 29 checks.

  • cargo test-fastly && cargo test-axum
  • cargo clippy-fastly && cargo clippy-axum
  • cargo fmt --all -- --check
  • JS tests: cd crates/trusted-server-js/lib && npx vitest run
  • JS format: cd crates/trusted-server-js/lib && npm run format
  • Docs format: cd docs && npm run format
  • WASM build: cargo build --package trusted-server-adapter-fastly --release --target wasm32-wasip1
  • Manual testing via fastly compute serve
  • Other: the full workflow set of this repository, run on this pull request at 61de267

Checklist

  • Changes follow AGENTS.md conventions
  • No unwrap() in production code (clippy denies unwrap_used in CI)
  • Uses log macros as AGENTS.md asks (not println!)
  • New code has tests
  • No secrets or credentials committed

On top of the source module series (#1380). Issue: #1340.

What this adds

What changes.

The React Server Components Flight rewriter leaves core for the Next.js module. crates/trusted-server-core/src/rsc_flight.rs is now crates/framework/nextjs/src/flight.rs, the fetch middleware framework.nextjs.flight for the media type text/x-component, registered beside framework.nextjs whenever [framework] module = "nextjs" selects the module and run where a [[fetch]] entry names it for that media type.

[[fetch]]
media_type = "text/x-component"
middleware = ["framework.nextjs.flight"]

How it works.

  • Core knows nothing of Flight. The publisher path's two Flight branches are gone, core's media type dispatch no longer excludes text/x-component, and the rewriter's five tests moved with it.
  • A middleware may say it moves the origin address. Core's text pass runs its own origin address replacer ahead of every non-HTML middleware, which would break a Flight row's length prefix before the rewriter saw it. A middleware that rewrites addresses itself says so through Middleware::moves_origin_address, false by default, and the publisher's text pass leaves that media type to it, whether or not an entry names the middleware, so a Flight response without an entry passes through as the origin sent it.
  • The address comes from the host. The middleware builds https://host and http://host from the origin host it is given, as the module's HTML rewriting does, so an origin whose address has a folder keeps that folder in a rewritten address.
  • A charset other than UTF-8 passes through. The text pass runs nothing on it and core's replacer is skipped, where the previous rewriter ran regardless.

Why

Every framework's code leaves core, and the stack gave a middleware a media type so that a change to a text response has a place. This was the last page change that predated it.

What breaks

A deployment that fronts a Next.js site names the rewriter in a [[fetch]] entry for text/x-component, as the guide shows. Without the module selected, a Flight response takes core's plain text handling, where core used to rewrite Flight for everyone. trusted_server_core::rsc_flight is gone.

The commits

  1. The Flight rewriter is a middleware of the Next.js module.
  2. A merge of the series below, which carries the fixes made lower in the stack.
  3. A merge of the series below, which carries the page origin change.
  4. The Flight middleware design states how it differs from core's replacement and its rollout, and takes its round 2 corrections.
  5. Later merges of the series below carry Tech Lab main at 6ef07ae.

Core rewrote the addresses in a React Server Components Flight stream
by its media type, in a module of its own, and the publisher path named
text/x-component to run it in place of the plain replacement it makes
in any other text body. That was the one piece of a framework's
handling left in core.

The rewriter moves to crates/framework/nextjs as the fetch middleware
framework.nextjs.flight, registered with the module and run where a
[[fetch]] entry for text/x-component names it. Core knows nothing of
Flight, and text/x-component is a media type an entry may cover, as
every other text type is.

A plain replacement in a length prefixed row leaves the old length, so
the middleware says with Middleware::moves_origin_address that it moves
the origin's address itself, and core leaves the addresses in a body of
the media types such a middleware handles to it, whether or not an
entry names it. With the module selected and no entry, a Flight
response passes through as the origin sent it. The registry answers
that question for a media type, and the publisher path's text pass
skips its replacer on that answer.

The middleware works from the origin host and the reader's host and
scheme its context carries, as the module's HTML middleware does, so a
folder in the origin's address stays in a rewritten address.

The stock middleware list, the configuration guide, the Next.js guide,
the RSC hydration guide, the template and the module's recorded page
settings carry the entry, and the design page records the change.
…ement and its rollout

Review round 1 found that the design called the middleware's rewriting the
same as before and the same as core's plain replacement, when the middleware
matches the origin host alone. The design now states the four differences
and why, the alternatives to the new method, the adapters, the refusals,
consent, failure and the rollout order. It also asks the reviewer what a
deployment that does not select the module should get, lists the cases no
test covers and records the review round. The template comment counts the
three entries it now shows.
Review round 2 approved the design with no blocking issues and found four
small errors. The hostname boundary rule holds only for a bare host, because
a longer name that begins with the origin host after https://, http:// or //
is changed by core's replacement, the old rewriter and the middleware alike.
The path paragraph now says that settings validation checks a path and that
the entry with the longest path makes an empty document where several cover
a request. The template comment limits the order that ts audit writes to the
HTML entries, and the review log and revision record carry the round.
@jwrosewell
jwrosewell added this pull request to stack #1401 October 15, 2026 17:56
@jwrosewell
jwrosewell removed this pull request from stack #1401 October 15, 2026 20:51
…t again

The Flight middleware moved the origin's address in a row with three
replacements in a row, for the `https://`, `http://` and protocol-relative
spellings, and then looked for a bare host at hostname boundaries. Each read
the previous one's output. When the request host begins with the origin
host, a later spelling matched inside an earlier replacement and the rest of
the request host was written again. With `publisher.example` served as
`publisher.example.localhost` over `http`, `https://publisher.example/news`
came out as `http://publisher.example.localhost.localhost.localhost/news`,
and served as `publisher.example:8443` over `https` it came out as
`https://publisher.example:8443:8443/news`.

`rewrite_utf8_bytes` now finds each occurrence of the origin host once, from
the start. One that follows `https://`, `http://` or `//` is moved together
with that spelling, and the text between those addresses is given to
`rewrite_bare_host_at_boundaries` for a bare host, so nothing written is read
again. The three origin spellings the rewriter kept are removed, and an empty
origin host leaves the row as it came.

The tests check that a request host which begins with the origin host, as a
`.localhost` name or with a port, is written once in a `T` row and a newline
row over both schemes, that a hyphenated host and a `www.` host over an apex
origin are written as before with a longer name left alone, and the rewrite
on its own, with an empty origin host.
@jwrosewell
jwrosewell removed this pull request from stack #1408 October 18, 2026 20:15
@jwrosewell jwrosewell closed this Oct 18, 2026
@jwrosewell jwrosewell reopened this Oct 18, 2026
@jwrosewell
jwrosewell added this pull request to stack #1418 October 19, 2026 12:18
@jwrosewell
jwrosewell removed this pull request from stack #1418 October 19, 2026 16:30
@jwrosewell jwrosewell closed this Oct 19, 2026
@jwrosewell jwrosewell reopened this Oct 19, 2026
@jwrosewell
jwrosewell added this pull request to stack #1421 October 19, 2026 17:38
@jwrosewell jwrosewell changed the title The Flight rewriter is a middleware of the Next.js module Move the Flight rewriter into the Next.js module Oct 19, 2026
@jwrosewell
jwrosewell removed this pull request from stack #1421 October 19, 2026 18:29
@jwrosewell
jwrosewell added this pull request to stack #1422 October 19, 2026 19:20
@jwrosewell
jwrosewell marked this pull request as ready for review October 19, 2026 20:24

This branch has not been deployed

No deployments
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.

Core rewrites Next.js RSC streams

1 participant