Skip to content

feat: add ssr: "data-only" - #2352

Open
lxsmnsyc wants to merge 1 commit into
mainfrom
feat/ssr-data-only
Open

lxsmnsyc wants to merge 1 commit into
mainfrom
feat/ssr-data-only

Conversation

@lxsmnsyc

@lxsmnsyc lxsmnsyc commented Oct 14, 2026 •

Copy link
Copy Markdown
Member

Closes #2287.

What this adds

A new value for the ssr option:

solidStart({ ssr: "data-only" });

The app renders on the client with render(), as with ssr: false, so there's no hydration and no app HTML from the server. The server still runs the route preloads for the requested page and streams their query results with the HTML shell. On the first render, the client's queries take those results instead of fetching them again. One response carries both the document and its data.

How it works

Server. loadInitialData runs the router's data-only pass, the same one single-flight mutations use. With event.router.dataOnly set, the router runs the matched routes' preloads without rendering pages, and query records each call in event.router.data. SPA builds already keep only the preloads in the server route tree.

The shell then renders with renderToStream instead of renderToString. Each query promise is passed to sharedConfig.context.serialize under a <Hydration> boundary, because Solid skips serialization inside <NoHydration>. This is the same serializer SSR uses for query data:

  • The shell doesn't wait for the data.
  • Each result streams in when it settles, including rejections and nested promises or streams.
  • <HydrationScript eventNames={[]}> defines _$HY.r. Nothing hydrates, so it doesn't capture events.

Responses from a query. query skips its own Response handling in data-only mode, so it's applied here:

  • Headers are copied to the page response, except the body headers (content-type, content-length, content-encoding).
  • A redirect that's known before the shell flushes becomes a real redirect, through the existing redirect handling. A thrown redirect isn't reported as an error.
  • A json() response sends its body.

Client. mount() points sharedConfig.has/sharedConfig.load at _$HY.r for the first render(), then restores them. The router's query already reads serialized data through those two functions on a cache miss, so no router change is needed. Later client navigations fetch as usual.

Limits

  • Data needs a streamed render (mode: "stream" or "async"). With mode: "sync" nothing is sent and the client fetches as before.
  • The root layout renders on the server during the data pass, as it does for single-flight mutations. If it throws there (for example, it reads window while rendering), the error is logged and the client fetches its data.
  • Query data uses Solid's serializer plugins, not serialization.plugins, the same as query data in SSR today.
  • This is app-wide. A per-page option could come later. Per page, clientOnly(() => import("./Page")) plus a route preload already gives the same behavior under SSR.

Also fixed

@solidjs/start/server in SPA mode didn't export getServerFunctionMeta, decorateHandler or decorateMiddleware. Any app importing one of them failed to build with ssr: false, for example the experiments fixture. They're exported now.

Verification

Production build of the basic fixture with a route whose preload calls four queries (data, json() with set-cookie, a throw, and a redirect on another route):

Check Result
Shell streams _$HY.r[...] for each query; content-type: text/html; set-cookie from the json() query
Client renders with the streamed data, Date and Map intact, error shown by the ErrorBoundary, 0 server function calls
Redirect route 302 with location: /co-b
Client navigation to the page fetches as usual (3 calls)
Dev server same: data in the shell, 0 calls
ssr: false unchanged: no _$HY, no server queries
  • 11 new unit tests (server collection, Response handling, rejections, sync render, root failure, client seeding).
  • packages/start: 251 tests, typecheck, build, format check.
  • apps/tests: unit, Playwright default (42), bundled-dev (42) and base (3) all pass.

Not fixed here: in SPA mode, the experiments fixture still returns 404 for its pages. Its [...404].tsx exports both a page and GET, and SPA builds treated that route as API-only. That's an existing problem, fixed separately in #2354.

🤖 Generated with Claude Code

The app renders on the client without hydration, as with `ssr: false`, but
the server runs the requested page's route preloads and streams their query
results with the HTML shell. The client's queries read those results on the
first render instead of fetching them again.

The server collects the queries with the router's data-only pass, the same
one single-flight mutations use, and streams them through Solid's own
serializer under a `<Hydration>` boundary. On the client, `mount()` points
`sharedConfig.has`/`load` at `_$HY.r` for the first `render()`, which is
where `query` already looks for serialized data.

Also export `getServerFunctionMeta`, `decorateHandler` and
`decorateMiddleware` from the SPA server entry, so SPA apps that import them
build.

Closes #2287

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Oct 14, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: fd9e80a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@solidjs/start Minor

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

@netlify

netlify Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for solid-start-landing-page ready!

Name Link
🔨 Latest commit fd9e80a
🔍 Latest deploy log https://app.netlify.com/projects/solid-start-landing-page/deploys/6acf10d1a25f1e95fc8b861e
😎 Deploy Preview https://deploy-preview-2352--solid-start-landing-page.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@pkg-pr-new

pkg-pr-new Bot commented Oct 14, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@solidjs/start@2352

commit: fd9e80a

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.

Support initial serialized page data with client-only rendering

1 participant