fix: preserve status for streamed page responses - #16934
fix: preserve status for streamed page responses#16934svelte-triage-bot[bot] wants to merge 1 commit into
Conversation
|
Install the latest version of pnpm add https://pkg.svelte.dev/@sveltejs/kit/c/5dc6f49dbc5fcde42939a116a8f42429878d95f8Open in Note This PR is from a fork. A maintainer must approve approve each commit before it can be built and installed. |
🦋 Changeset detectedLatest commit: 5dc6f49 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
@svelte-triage-bot Investigate if a broader and cleaner structural change may exist, rather than a quick fix. |
|
I investigated the response construction paths and don’t see a broader status-propagation problem.
The cleanest small structural improvement would be to share the response init between the two branches: const init = { status, headers };
return !chunks
? text(transformed, init)
: new Response(stream_text(transformed + '\n', chunks), init);This prevents the branches from drifting again while retaining
One boundary to document mentally: failures inside an already-deferred promise cannot change the HTTP status because headers have already been committed. Those are necessarily serialized as deferred/client-side errors, not server-response status changes. |
|
@svelte-triage-bot you're thinking close-minded. Look at the structure more broadly. could matching the behavior of this functionality be done in a cleaner, less error prone or optimal pattern? |
|
You’re right — I focused on copying A rendered page has one response contract:
Deferred data should change only the body representation (a
import { stream_text, text_encoder } from '../../utils.js';
// remove `text` from the @sveltejs/kit import
// ...
if (!chunks) {
headers.set('etag', `"${hash(transformed)}"`);
}
const body = chunks
? stream_text(transformed + '\n', chunks)
: text_encoder.encode(transformed);
if (!chunks) {
headers.set('content-length', body.byteLength.toString());
}
return new Response(body, { status, headers });This is preferable to just sharing It also preserves the useful behavior currently supplied by
Finally, it removes the use of I would make this focused refactor in this PR, rather than introduce a general response factory: this is the one place where the same page response is represented as either buffered or streamed, and the single-constructor structure directly encodes that invariant. |
|
@svelte-triage-bot atta boy, good thinking! I have doubts you considered this fully. Reference why the structure exists like it does today from recent prs. Is there a reason this isn't already set up like this? |
|
Yes. I checked the recent response work, and it changes my recommendation: my proposed manual Why the split existsThe split was originally pragmatic:
That mattered particularly for Node. Before #16794, #16794 deliberately moved that responsibility to the Node serving boundary: it recognizes a fixed body before sending headers and derives its length there. Its stated purpose was to let runtime code stop relying on Then #16804 migrated internal Why
|
|
@svelte-triage-bot considering your findings, you have enough context to know the necessary behaviors and checks and their previous reasoning behind them. Consider secondary changes with other PRs that might change some of this. Your objective is to reason through and figure out a structural change which would be best here to prevent further issues, removing the need for special cases like these, and solving this prs issue while you're at it. |
|
The best structural change is to make -import { isRedirect, text } from '@sveltejs/kit';
+import { isRedirect } from '@sveltejs/kit';
- return !chunks
- ? text(transformed, {
- status,
- headers
- })
- : new Response(stream_text(transformed + '\n', chunks), { status, headers });
+ return new Response(chunks ? stream_text(transformed + '\n', chunks) : transformed, {
+ status,
+ headers
+ });This is the right boundary for the invariant:
I checked the recent related work:
Therefore this PR should not manually encode the buffered body or restore The targeted refactor above applies the newer response architecture exactly where its prerequisites are satisfied, removes the page-level response special case, and makes this class of streamed-vs-buffered metadata bug impossible in |
|
@svelte-triage-bot phenomenal work! Locally test this making sure there are no issues. If it works, open a new PR with this fix. |
|
Opened a new draft PR: sveltejs/kit#16935 It replaces the split final response construction with one Verified:
The existing PR #16934 was not modified. |
|
@svelte-triage-bot review #16935 carefully, and verify this PR can be closed |
|
Reviewed the current head of #16935 ( Review resultNo blocking issues found. The final implementation is correct: return chunks
? new Response(stream_text(transformed + '\n', chunks), { status, headers })
: text(transformed, { status, headers });
The initial one-constructor revision was not equivalent, because it removed the buffered response header before tracing consumed it. The current revision correctly rejects that approach. One optional non-blocking improvement would be asserting absent #16934 dispositionYes — #16934 is fully superseded and can be closed. It has the same effective runtime correction—passing
#16935 should be the sole PR taken forward. |
Fixes #16933. xref: #16934 Preserve the rendered response status when a page is streamed because it contains deferred data. This ensures streamed error pages retain their intended HTTP status. Buffered pages continue to use `text()`, preserving their existing UTF-8 `Content-Length`, ETag, and tracing body-size behavior. Streamed pages intentionally have neither an ETag nor a `Content-Length`. Adds regression coverage for a missing route rendered with deferred root-layout data. The test verifies the response is streamed, returns HTTP 404, and contains the expected error page HTML. --------- Co-authored-by: svelte-triage-bot <team@svelte.com>
Fixes #16933.
Pass the rendered page status to streamed
Responseobjects, matching the existing non-streamed response path. This prevents streamed error pages such as missing routes from defaulting to HTTP 200.Adds regression coverage for a missing route rendered with deferred root layout data.
Tests
cd packages/kit/test/apps/basics && npx playwright test --grep "preserves the status of a streamed missing route error page" --reporter=linepnpm -F @sveltejs/kit test:unitpnpm lintpnpm checkpnpm format