feat(ssr): defer serving to external servers, inject dev CSS on the response - #284
Closed
brenelz wants to merge 1 commit into
Closed
feat(ssr): defer serving to external servers, inject dev CSS on the response#284brenelz wants to merge 1 commit into
brenelz wants to merge 1 commit into
Conversation
…esponse Turnkey SSR assumes it owns the server. When a server integration (nitro, a Cloudflare worker, a custom harness) drives the build and serves requests, two assumptions break. The ssr build input was overwritten with virtual:solid-ssr-handler unconditionally, leaving the integration with no server entry — under nitro no renderer route gets registered and every page 404s in preview. A configured environments.ssr.build.rollupOptions.input now signals external ownership: entries, handler and client manifest config are still contributed, but the ssr input, dist/* outDirs, builder flag and dev HTML middleware are left alone. Entry CSS is inlined by that dev HTML middleware, so it stops reaching the page once something else serves and dev SSR flashes unstyled. The styles are still collected here, in the Vite process where the module graph lives, and spliced into the response before </head> on its way back out — as a pre middleware, since an external server generally answers before Vite's own middlewares, and on the response rather than through the handler, so it works whether or not the integration renders via handleRequest. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: bd1e93d 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 |
commit: |
Contributor
Author
|
Hmm not sure i really like this either |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Alternative to #283 — same first half, different answer for dev CSS. Opened separately so the two mechanisms can be compared; only one should land.
Both PRs fix the same two problems that surface when a server integration (nitro, a Cloudflare worker, a custom harness) owns the server under turnkey
ssr: {}.Shared with #283: the ssr build input is overwritten unconditionally
config()always points the ssr environment atvirtual:solid-ssr-handler(plusdist/*outDirs andbuilder). An integration with its own server entry is left without one — nitro'sgetEntryfinds nothing, registers no renderer route, and every page 404s in preview. A user-configuredenvironments.ssr.build.rollupOptions.inputnow signals external ownership, and turnkey leaves the ssr input, outDirs,builderflag and dev HTML middleware alone while still contributing entries, handler and client manifest config. Plain-Vite turnkey apps don't set that input, so nothing changes for them.The difference: how dev entry CSS reaches the page
Entry stylesheets are inlined by the dev HTML middleware, which never runs once something else serves — so dev SSR streams unstyled markup and flashes.
options.devHeadfallback).collectDevStyleslive — and splices them into the streamed HTML before</head>as it flows back out, via a pre middleware.Why I think this one is better:
handleRequest. It works for any external server, including integrations whose entry callsrenderToStreamdirectly. feat(ssr): defer serving and build wiring to external servers #283 only works if the entry routes through the generated handler.workerdthat may loop back into the Worker rather than reaching Vite — a caveat I couldn't discharge. This has no such question.The cost is that it patches
res.write/res.endto rewrite a streamed body, and dropscontent-lengthwhen it injects. Two details worth review: it only rewrites the first chunk containing</head>(fine forrenderToStream, whose shell arrives in one chunk, but it does not stitch a</head>split across chunk boundaries), and chunks arrive asUint8Arrayfrom web-stream bodies rather thanBuffer, which the transform handles explicitly.Testing
Against a Solid 2 + nitro app (
vite-plugin-solid@3.0.0-next.16,nitro@3.0.260610-beta, Vite 8.0.10) using generated entries and a smallsrc/ssr.tsadapter exposing the handler as nitro's{ fetch }service:<head>, styled first paint; exactly one<style>tag after hydration (Vite's client adopts the SSR'ddata-assettwin viadevStylePatch); clean hydration, no console errors; server functions round-trip. Appending a rule to a stylesheet appears in the next SSR response and disappears when reverted, so HMR edits stay current./200 with hashed client entry and stylesheet injected; no dev-only code in the server bundle.Note
The trigger is a heuristic; an explicit option (
ssr: { serve: false }or similar) may be preferable, happy to switch. Also related to #281: theif (externalServer) returnguard skips the middleware whosessrLoadModulecall is what crashes there, so this covers turnkey's half of that issue for any setup that trips the trigger — but not the server-functions middleware, which still needs the Environment API migration.🤖 Generated with Claude Code