Repository navigation
fix(web): give pending fragments one task before the shell flushes - #4147
Conversation
renderToStream granted pending fragments only a handful of microtask turns before flushing the shell, so a read whose retry chain is longer than that (a server function over in-memory data, a cache answering on setImmediate) shipped its Loading fallback, a <template> and a reveal, causing layout shift even though the data was ready. The shell now waits one task (setImmediate) after the drain settles while fragments are pending. Renders with nothing pending are unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011c7QLxhYyzAcek32Sun3pK
🦋 Changeset detectedLatest commit: 46db1cf The changes in this PR will be included in the next version bump. This PR includes changesets to release 14 packages
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 |
Size (brotli, eager entry chunk)
|
Coverage Report for CI Build 39205738161Coverage remained the same at 77.555%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
Merging this PR will not alter performance
|
Requested by Ryan · project thread
Summary
Before:
renderToStreamgave pending fragments only a few microtask turns (MIN_DRAIN_TURNS) before it flushed the shell. If an async read took more than about three microtasks, itsLoadingfallback shipped along with a<template>and a reveal script, even when the data was effectively instant. That includes a"use server"query over in-memory data going through the router. On a store listing and product page this showed up as a layout shift when the reveal pushed the footer down a screen (CLS 0.75 in most Lighthouse runs).After: once the microtask drain settles and fragments are still pending, the shell waits one more task (
setImmediate, or the existingdeferFlushfallback on hosts without it) before flushing. Data ready by then renders inline with no fallback, template or reveal. Renders with nothing pending flush exactly as before. Genuinely pending I/O pays one task of first-byte latency. 1.x waited a task before every shell flush.How: one
gracedflag per flush attempt inscheduleFlush. When the drain would otherwise call the flush andregistry.sizeis non-zero, the attempt re-queues itself once ondeferFlushand drains again.How did you test this change?
test/server/shell-task-grace.spec.tsx: data ready after a 20-step microtask chain, and data ready onsetImmediate, both render inline in the first chunk. Data gated until after the first chunk still streams behind its fallback. The two inline cases fail onnextand pass here.frame-plain-bound.spec.tsx: the live-response test pushed its first value before the shell had flushed and expected it to stream. It now waits for the shell chunk first, like the other tests in that file.pnpm run build, thenpnpm turbo run test test-types typecheck --continue: everything passes exceptserver-functions-adapter-request"rebuild failure", which also fails onnextunder Node 22.22 locally. The web hydrate suite (511 tests) and client suite (1383) pass.@solidjs/web): onnext, all three pages shipLoading...plus a<template>. With this change they render inline (0 templates, about 2 kB smaller each).Generated by Claude Code