diff --git a/src/routes/(3)building-apps/(0)app-structure.mdx b/src/routes/(3)building-apps/(0)app-structure.mdx index b9ee3ba94..ae644812f 100644 --- a/src/routes/(3)building-apps/(0)app-structure.mdx +++ b/src/routes/(3)building-apps/(0)app-structure.mdx @@ -12,7 +12,7 @@ Those two components are the whole application surface. The plugin's start mode generates the rest: the server entry that renders ``, the client entry that mounts or hydrates it, and the request handler that serves both. Most apps need only `App.tsx` and `Document.tsx`, and the first two sections below are about those files. -The sections on generated entries and `start.setup` are for projects that need to change the defaults, such as an authored server entry or a router that must load before the render starts. +The sections on generated entries and `start.serverRenderSetup` are for projects that need to change the defaults, such as an authored server entry or a router that must load before the render starts. ## Three rendering modes, one layout @@ -159,13 +159,13 @@ Start mode does not select a router. Mount the router or its provider inside `App`, the way both router variants of the templates do, and route modules and route-table generation stay with the router you chose. Some routers need an instance bound to the current request so they can load the matched routes before the server render begins. -`start.setup` names a server-only module for that: +`start.serverRenderSetup` names a server-only module for that: ```ts // vite.config.ts solid({ start: { - setup: "./src/setup.tsx", + serverRenderSetup: "./src/setup.tsx", }, ssr: true, }); @@ -176,7 +176,7 @@ The generated server entry calls it after the middleware chain has dispatched to It may return a component, nothing, or a promise of either: a returned component renders in `App`'s place inside `Document`, and no return keeps ``. The browser-side `App` must produce the same router tree, or hydration will not match. -`start.setup` runs only for page renders made by a generated server entry under `ssr: true`. +`start.serverRenderSetup` runs only for page renders made by a generated server entry under `ssr: true`. It is ignored without `ssr`, and combining it with an authored server entry is an error. [Integrate a router](/routing/integrate-a-router) shows how the two supported routers use this hook and when a project needs it at all. @@ -204,22 +204,22 @@ Production builds wrap the generated entries in a default error boundary that lo Read the server log for the real error. Set `start.productionErrorBoundary: false` when middleware owns error handling; [Middleware and API routes](/building-apps/middleware-and-api-routes#catching-errors) shows that middleware. -### `start.setup only applies to generated entries` +### `start.serverRenderSetup only applies to generated entries` -The project has an authored `src/entry-server.*` (or `start.entries.server`) and `start.setup` at the same time. -Call the setup step from your own `render()` and remove `start.setup`, or remove the authored entry. +The project has an authored `src/entry-server.*` (or `start.entries.server`) and `start.serverRenderSetup` at the same time. +Call the setup step from your own `render()` and remove `start.serverRenderSetup`, or remove the authored entry. ## Loading instrumentation first An error monitor or a tracing SDK on the server patches `node:http` and the other modules it observes, so it has to run before those modules are imported. Writing `import "./instrument"` at the top of an entry does not achieve that in ESM: static imports are hoisted and evaluated in dependency order, so the entry's own dependencies load first. -`start.instrument` names a server-only module the plugin awaits before anything else in the server graph: +`start.serverSetup` names a server-only module the plugin awaits before anything else in the server graph: ```ts // vite.config.ts solid({ start: { - instrument: "./src/instrument.ts", + serverSetup: "./src/instrument.ts", }, ssr: true, }); @@ -245,8 +245,8 @@ Keep code splitting on in the server build (the default); inlining dynamic impor - Render `{props.children}` in the document body, or the app never appears on the server. - The static `` in `Document` is the fallback; per-page titles come from head metadata. - Author `entry-server` and `entry-client` together, and only when the generated pair cannot produce the response you need. -- Use `start.setup` when a router must load against the request before the server render; it applies only to generated entries under `ssr: true`. -- Use `start.instrument` for a module that must run before the server graph loads, such as a monitoring SDK's `init()`; a static import at the top of an entry does not run first. +- Use `start.serverRenderSetup` when a router must load against the request before the server render; it applies only to generated entries under `ssr: true`. +- Use `start.serverSetup` for a module that must run before the server graph loads, such as a monitoring SDK's `init()`; a static import at the top of an entry does not run first. ## Next steps diff --git a/src/routes/(3)building-apps/(7)deployment.mdx b/src/routes/(3)building-apps/(7)deployment.mdx index 68a80fc96..4afd2f5bd 100644 --- a/src/routes/(3)building-apps/(7)deployment.mdx +++ b/src/routes/(3)building-apps/(7)deployment.mdx @@ -385,7 +385,10 @@ function isFromOldBuild(err: unknown) { ``` A function whose id survives a deploy can still take other arguments or return another shape. -To catch that too, give the server a [`buildId`](/reference/solid-web/server-functions/host-configuration#buildid) per deploy, such as the commit hash, and send the same id from the client in the `X-Server-Function-Build` header through `prepareRequest`. +To catch that too, the server checks a [`buildId`](/reference/solid-web/server-functions/host-configuration#buildid) against the `X-Server-Function-Build` header the client sends. +`@solidjs/vite-plugin` wires both sides in production builds: the client build stamps every non-GET call with a fresh id per build, and the server build is configured with the same id, so a new deploy refuses calls from tabs still running the previous one. +An app's own `prepareRequest` hook still runs, after the plugin's. +Without the plugin, give the server a `buildId` per deploy, such as the commit hash, and send the same id from the client in that header through `prepareRequest`. A call from another build is answered `409` without running the function, and rejects with the same `unknownFunction: true`. ## Common problems diff --git a/src/routes/(4)routing/(2)tanstack/(0)index.mdx b/src/routes/(4)routing/(2)tanstack/(0)index.mdx index 8164d2649..86825589d 100644 --- a/src/routes/(4)routing/(2)tanstack/(0)index.mdx +++ b/src/routes/(4)routing/(2)tanstack/(0)index.mdx @@ -168,7 +168,7 @@ For forms that must work without JavaScript, the `fullstack` shape's Solid Route ## What the server does per request -The plugin's `start.setup` hook is the seam that lets a router Solid does not own drive streaming SSR. +The plugin's `start.serverRenderSetup` hook is the seam that lets a router Solid does not own drive streaming SSR. `src/setup.tsx` runs for each SSR request: ```tsx @@ -278,7 +278,7 @@ The one adjustment is where API routes live: `src/routes` belongs to TanStack Ro ## Limits to know -- `start.setup` runs only with the plugin's generated server entry and only when `ssr` is on. +- `start.serverRenderSetup` runs only with the plugin's generated server entry and only when `ssr` is on. An authored server entry has to prepare the router and render it itself. - TanStack's own SSR protocol (`RouterServer`, `RouterClient`, and the `$_TSR` stream handlers) expects to own the HTML stream, and under start mode the plugin owns it. The template does not use that protocol; the hydration-registry transfer above is the native channel for this setup. @@ -293,12 +293,12 @@ The one adjustment is where API routes live: `src/routes` belongs to TanStack Ro - Read `query.data` directly and use `isPending` for refetch state; skip the `isLoading` guards. - Call mutations through `useMutation` with an arrow around the server function, and return `reload({ revalidate })` to scope what single-flight collects. - Keep `staleTime` above zero so hydrated and single-flight data count as fresh. -- Build a fresh router and `QueryClient` per request in `start.setup`, and copy a redirect or status from `router._serverResult` onto the response. +- Build a fresh router and `QueryClient` per request in `start.serverRenderSetup`, and copy a redirect or status from `router._serverResult` onto the response. - Leave `disableGlobalCatchBoundary: true` in place so errors and redirects reach the app's own boundaries during SSR. ## Next steps - [Server functions](/building-apps/server-functions): the reads and mutations wrapped in `queryOptions` and `useMutation`, including why `GET` matters. -- [Integrate a router](/routing/integrate-a-router): the `start.setup` and flight-data hooks this template is built on, for adapting the pattern to another router. +- [Integrate a router](/routing/integrate-a-router): the `start.serverRenderSetup` and flight-data hooks this template is built on, for adapting the pattern to another router. - [Choose a rendering mode](/guides/choose-a-rendering-mode): the `ssr: false` posture, a static shell plus API server, works for this shape too. - [Middleware and API routes](/building-apps/middleware-and-api-routes): the `src/api` handlers, and why they live outside `src/routes` in this shape. diff --git a/src/routes/(4)routing/(3)integrate-a-router.mdx b/src/routes/(4)routing/(3)integrate-a-router.mdx index 0a1c7f4df..30ab090fe 100644 --- a/src/routes/(4)routing/(3)integrate-a-router.mdx +++ b/src/routes/(4)routing/(3)integrate-a-router.mdx @@ -20,13 +20,13 @@ A router that does the same needs nothing else from the platform for a synchrono ### Prepare an instance per request -A router that must do asynchronous work before rendering, such as creating a request-bound instance and awaiting its route loaders, uses the `start.setup` option: +A router that must do asynchronous work before rendering, such as creating a request-bound instance and awaiting its route loaders, uses the `start.serverRenderSetup` option: ```ts // vite.config.ts solid({ start: { - setup: "./src/setup.tsx", + serverRenderSetup: "./src/setup.tsx", }, ssr: true, }); @@ -52,14 +52,14 @@ The browser-side `App` must produce the matching router tree so hydration lines The [TanStack Router](/routing/tanstack#what-the-server-does-per-request) page shows a complete `setup.tsx`, including how it copies a redirect or a `404` from the router onto the response. :::caution[Where the hook runs and where it does not] -`start.setup` runs only when a generated server entry renders an SSR request. +`start.serverRenderSetup` runs only when a generated server entry renders an SSR request. It is ignored when `ssr` is off, and configuring it together with an authored `entry-server` is a build error, because an authored entry already owns its render function and must perform the same preparation itself. ::: ### Middleware shares the event The separate `start.middleware` option installs a fetch-style chain in front of page renders, server-function calls, API routes, and no-JavaScript form posts. -The chain and `start.setup` receive the same request event, so a value a middleware stores in `event.locals`, such as a session, is visible to the router instance the setup hook builds. +The chain and `start.serverRenderSetup` receive the same request event, so a value a middleware stores in `event.locals`, such as a session, is visible to the router instance the setup hook builds. [Middleware and API routes](/building-apps/middleware-and-api-routes) covers the chain itself. ## Integrate single-flight mutations @@ -119,14 +119,14 @@ Its `QueryClientProvider` subscribes as `"sq"` while mounted, the server hook bu ## Common problems -### The build fails with `start.setup only applies to generated entries` +### The build fails with `start.serverRenderSetup only applies to generated entries` -The project has an authored `entry-server` and a `start.setup` option at the same time. -Remove one: either delete the authored entry so the generated one runs the hook, or move the preparation into the authored entry's own render function and drop `start.setup`. +The project has an authored `entry-server` and a `start.serverRenderSetup` option at the same time. +Remove one: either delete the authored entry so the generated one runs the hook, or move the preparation into the authored entry's own render function and drop `start.serverRenderSetup`. ### The setup module never runs -`start.setup` is server-mode only. +`start.serverRenderSetup` is server-mode only. With `ssr: false` there is no per-request app render to prepare, so the option is accepted and ignored. Check the `ssr` flag in `vite.config.ts` before looking at the module. @@ -143,8 +143,8 @@ The unnamed server hook pairs with the one-argument `subscribeFlightData(consume ## Recap - Read the request URL from the request event during SSR; the handler runs the render inside that event's scope. -- Use `start.setup` for a router that needs a per-request instance or asynchronous loading before render, and return the component to render in `App`'s place. -- `start.setup` applies only to generated server entries with `ssr` on; an authored entry does the same work itself. +- Use `start.serverRenderSetup` for a router that needs a per-request instance or asynchronous loading before render, and return the component to render in `App`'s place. +- `start.serverRenderSetup` applies only to generated server entries with `ssr` on; an authored entry does the same work itself. - Values a middleware stores in `event.locals` are visible in the setup hook, because both receive the same request event. - Opt in to single-flight on the client with `subscribeFlightData`; the header is sent on non-`GET` calls while a consumer is registered. - Register the server-side `collectFlightData` hook from the `serverFunctions.configure` module so it loads before the first dispatch. diff --git a/src/routes/(5)guides/(15)observability.mdx b/src/routes/(5)guides/(15)observability.mdx index 702da0f1a..e13ce5bfc 100644 --- a/src/routes/(5)guides/(15)observability.mdx +++ b/src/routes/(5)guides/(15)observability.mdx @@ -114,7 +114,7 @@ The context then carries the request's `event`, and the hook's return value is i The hook runs inside the request scope, so `getRequestEvent()` works in it. `renderToStream(App, { onError })` and `renderToString(App, { onError })` take a per-request hook that wins over the ambient one for that request. -A monitoring SDK's `init()` has to run before the modules it patches load; the plugin's [`start.instrument`](/building-apps/app-structure#loading-instrumentation-first) option is where a plugin-hosted app puts it, and the module above is the shape it takes. +A monitoring SDK's `init()` has to run before the modules it patches load; the plugin's [`start.serverSetup`](/building-apps/app-structure#loading-instrumentation-first) option is where a plugin-hosted app puts it, and the module above is the shape it takes. :::danger[The return value goes on the wire] The hook receives the error as thrown. diff --git a/src/routes/(6)migration/(2)from-solid-start.mdx b/src/routes/(6)migration/(2)from-solid-start.mdx index d8b3ee08d..9f3e3eb48 100644 --- a/src/routes/(6)migration/(2)from-solid-start.mdx +++ b/src/routes/(6)migration/(2)from-solid-start.mdx @@ -177,7 +177,7 @@ export default defineConfig({ ``` `start: true` and `start: {}` enable the same mode. -Use the object form only when the application needs `app`, `entries`, `document`, `middleware`, `setup`, or `externalServer`; typed environment variables use the top-level `env` option. +Use the object form only when the application needs `app`, `entries`, `document`, `middleware`, `serverSetup`, `serverRenderSetup`, or `externalServer`; typed environment variables use the top-level `env` option. The `extensions` entry makes the Solid transform accept route module IDs generated with query suffixes by `filesystem-routing`. Map old configuration deliberately: @@ -206,7 +206,7 @@ Generated SSR entries: The start-mode handler creates the request event used by middleware, page rendering, and server functions. -Keep generated entries unless the old entries contain behavior that cannot move into `App`, `Document`, middleware, `start.setup`, or a server function. +Keep generated entries unless the old entries contain behavior that cannot move into `App`, `Document`, middleware, `start.serverRenderSetup`, or a server function. When authored entries are necessary, rewrite both files as a pair. The server entry must export `render(request?, context?)`, and the client entry must hydrate the same document tree. @@ -284,7 +284,7 @@ See the [file-system route conventions](/reference/filesystem-routing/convention For TanStack Router, start from the pinned `fullstack-tanstack` template. Its router plugin owns `src/routes` and generates `routeTree.gen.ts`. -For SSR, `start.setup` creates and loads a request-specific router before the generated entry renders. +For SSR, `start.serverRenderSetup` creates and loads a request-specific router before the generated entry renders. Do not reuse SolidStart's router server or client entry protocol. See the [TanStack Router integration](/routing/tanstack). diff --git a/src/routes/reference/(5)vite-plugin-solid/(2)start.mdx b/src/routes/reference/(5)vite-plugin-solid/(2)start.mdx index b972ef622..cd9b7132b 100644 --- a/src/routes/reference/(5)vite-plugin-solid/(2)start.mdx +++ b/src/routes/reference/(5)vite-plugin-solid/(2)start.mdx @@ -104,15 +104,25 @@ type Middleware = ( Middleware runs in array order inside the request-event scope. The middleware fronts every request dispatched by the generated handler, including pages and the server-function endpoint. -### `setup` +### `serverSetup` - **Type:** `string` - **Default:** `undefined` -Specifies a server-only module whose default export prepares the application before generated SSR begins. +Specifies a server-only module the plugin runs to completion once, before anything else in the server graph loads: the app, the middleware, `@solidjs/web`, and every dependency. +It is the place for a monitoring SDK's `init()`, which has to run before the modules it patches load. +The module needs no exports and may use top-level `await`. +It is the server counterpart of `clientSetup`. + +### `serverRenderSetup` + +- **Type:** `string` +- **Default:** `undefined` + +Specifies a server-only module whose default export runs before every generated server render. ```ts -type Setup = ( +type ServerRenderSetup = ( event: RequestEvent, App: Component ) => Component | void | Promise<Component | void>; @@ -120,8 +130,8 @@ type Setup = ( Return a component to replace `App` for that request. Return nothing to keep `App`. -The plugin ignores `setup` in client mode and rejects it when SSR uses authored entries. -The setup function does not run for server-function endpoint dispatch. +The plugin ignores `serverRenderSetup` in client mode and rejects it when SSR uses authored entries. +The function does not run for server-function endpoint dispatch. ### `productionErrorBoundary`