Skip to content

SolidStart

SolidStart builds through Vite: the solidStart() plugin in your vite.config.ts owns routing, SSR, and the server entry, and a single vite build produces the whole app. That makes it a pure-Vite project, so Cloudflare.Website.Vite deploys it directly — no adapter config, no Wrangler file, no manual entrypoint.

Using TanStack Start with Solid instead? See TanStack Start.

Your vite.config.ts is just the SolidStart plugin — Alchemy layers its Cloudflare integration on top when it builds:

vite.config.ts
import { defineConfig } from "vite";
import { solidStart } from "@solidjs/start/config";
export default defineConfig({
plugins: [solidStart()],
});

Because SolidStart is fully expressed as a Vite plugin, Alchemy’s programmatic vite build picks up the client assets and the SSR server bundle from this one config — the server bundle becomes the deployed Worker, the client output becomes its static assets.

Yield Cloudflare.Website.Vite from your Stack with nodejs_compat enabled:

alchemy.run.ts
import * as Alchemy from "alchemy";
import * as Cloudflare from "alchemy/Cloudflare";
import * as Effect from "effect/Effect";
export default Alchemy.Stack(
"CloudflareSolidStartExample",
{
providers: Cloudflare.providers(),
state: Cloudflare.state(),
},
Effect.gen(function* () {
const worker = yield* Cloudflare.Website.Vite("CloudflareSolidStart", {
compatibility: {
flags: ["nodejs_compat"],
},
});
return {
url: worker.url,
};
}),
);

The nodejs_compat flag is required because SolidStart’s SSR server bundle uses Node APIs at runtime; without it the deployed Worker fails to start.

You don’t need SolidStart to server-render Solid. The examples/cloudflare-solidjs-ssr example uses plain vite-plugin-solid with SSR enabled and declares an ssr environment whose input is a hand-written server entry:

vite.config.ts
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { defineConfig } from "vite";
import solidPlugin from "vite-plugin-solid";
const __dirname = dirname(fileURLToPath(import.meta.url));
export default defineConfig({
plugins: [solidPlugin({ ssr: true })],
environments: {
ssr: {
build: {
emptyOutDir: false,
rolldownOptions: {
input: resolve(__dirname, "src/entry-server.tsx"),
},
},
},
},
});

src/entry-server.tsx default-exports a standard Worker fetch handler that calls Solid’s renderToStringAsync and injects the result (plus the hydration script) into the HTML template — that file becomes the Worker entry, while the client build ships as static assets.

Because the Worker itself renders every page, requests must reach it before the asset layer answers — set assets.runWorkerFirst:

alchemy.run.ts
import * as Alchemy from "alchemy";
import * as Cloudflare from "alchemy/Cloudflare";
import * as Effect from "effect/Effect";
export default Alchemy.Stack(
"CloudflareSolidJSSSRExample",
{
providers: Cloudflare.providers(),
state: Cloudflare.state(),
},
Effect.gen(function* () {
const worker = yield* Cloudflare.Website.Vite("SolidJSSrr", {
compatibility: {
flags: ["nodejs_compat"],
},
assets: {
runWorkerFirst: true,
},
});
return {
url: worker.url,
};
}),
);

With runWorkerFirst: true the SSR handler sees every request first and delegates to the ASSETS binding itself for static files — without it, a request matching a built asset (like /index.html) would be served directly and never hit your renderer.

Terminal window
bun alchemy deploy

Alchemy runs the Vite build, uploads the client assets, deploys the SSR bundle as the Worker, and prints the stack’s url output. Inputs are content-hashed, so re-deploying an unchanged project skips the build entirely.

Terminal window
bun alchemy dev

alchemy dev boots Vite’s own dev server programmatically — HMR and instant module updates work as in a plain SolidStart project, while any bound backend resources are the real cloud resources.

Both env channels of the Vite resource apply unchanged: VITE_-prefixed env entries are inlined into the client bundle as import.meta.env.<KEY> at build time, and everything else attaches to the SSR Worker as runtime bindings. See the Vite resource page for how each channel works.