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.

Declare the site as a module-level const (rather than inline in the Stack) and derive the typed shape of its bindings from it:

alchemy.run.ts
import * as Cloudflare from "alchemy/Cloudflare";
export const Website = Cloudflare.Website.Vite("Website");
export type WebsiteEnv = Cloudflare.InferEnv<typeof Website>;

SolidStart’s SSR server bundle uses Node APIs at runtime — the nodejs_compat compatibility flag covers that, and it is enabled by default for every Worker, so there is nothing to pass.

Yield the class from your Stack and return its URL:

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

Both env channels of the Vite resource apply unchanged: VITE_-prefixed 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:

alchemy.run.ts
import * as Config from "effect/Config";
export const Uploads = Cloudflare.R2.Bucket("Uploads");
export const Website = Cloudflare.Website.Vite("Website", {
env: {
UPLOADS: Uploads,
API_KEY: Config.redacted("API_KEY"),
},
});

Uploads is a description, not a deploy — Alchemy provisions the real bucket because the Website binds it. Config.redacted reads API_KEY from your environment at deploy time and binds it as a Worker secret — see Secrets & env.

WebsiteEnv (from Declare the Website) is the typed shape of the runtime bindings — import it wherever your server code reads the Worker env. See the Vite resource page for how each env channel works.

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
export const SolidSsr = Cloudflare.Website.Vite("SolidJSSsr", {
assets: {
runWorkerFirst: true,
},
});

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.