Skip to content

feat(start): add start.clientSetup to run client code before hydrate - #453

Open
lxsmnsyc wants to merge 1 commit into
nextfrom
feat/419-client-setup
Open

lxsmnsyc wants to merge 1 commit into
nextfrom
feat/419-client-setup

Conversation

@lxsmnsyc

Copy link
Copy Markdown
Member

Closes #419.

Adds start.clientSetup, a module that the generated client entry runs before hydrate() or render().

solid({ start: { app: 'src/App.tsx', clientSetup: './src/client-setup.ts' } })

Changes

  • start.clientSetup is a path inside the Vite root. It is validated the same way as start.instrument and start.setup.
  • The generated client entry imports it first, before @solidjs/web and the app.
  • It runs before hydrate() with ssr: true and before render() without it, so the option keeps working when ssr changes.
  • A top-level await in the module delays hydrate() or render() until it settles.
  • The module is added to the dependency scan entries, so its dependencies are pre-bundled at startup.
  • Setting it together with an authored entry-client is a config error, the same rule as start.setup.
  • Editing the module in dev reloads the page.
  • README: a new clientSetup section. The list of start options now also includes the missing instrument.

Design choice

The module is a static first import, not await import() as start.instrument does on the server.

  • With await import(), the app would move into a separate chunk. The server would no longer preload it or link its CSS, which causes a waterfall and unstyled content.
  • The trade-off: a synchronous setup module finishes before any app module runs. An async one only finishes before hydrate() or render(), because the app's modules evaluate while it awaits.
  • The use cases in the issue need no more than that. The JSDoc and README say so.
  • <ClientEntry /> (option 2 in the issue) is not included. Placing the entry script in the document is a separate design decision.

Testing

  • Added a client-setup mode to examples/start-ssr/test/run.mjs. In Chrome, for dev and prod, it checks that:
    • setup starts first;
    • hydrate() waits for its top-level await;
    • it runs once;
    • hydration is clean and the app's stylesheet is still linked;
    • editing the module in dev reloads the page.
  • It also checks the generated entry in both modes and both config errors. It fails without the change.
  • start-ssr modes client-setup 20/20, dev 80/80, entries 21/21 and prod 77/77 pass. The start-client suite passes 70/70.
  • pnpm test:unit and tsc --noEmit pass.

🤖 Generated with Claude Code

- The generated client entry imports the configured module ahead of the
  app, so it runs before hydrate() in SSR mode and before render() in
  client mode.
- A top-level await in the module delays hydrate()/render() until it
  settles.
- It is a static import, so the app stays in the entry chunk and the
  server still preloads its chunks and links its CSS.
- The module joins the dep-scan entries.
- An authored entry-client together with the option is a config error.
- The README start options list now also names `instrument`.

Closes #419.

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

changeset-bot Bot commented Oct 19, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: fe5be7b

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

This PR includes changesets to release 1 package
Name Type
@solidjs/vite-plugin Patch

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

@pkg-pr-new

pkg-pr-new Bot commented Oct 19, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@solidjs/vite-plugin@453

commit: fe5be7b

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.

1 participant