Deploying SolidStart to Cloudflare Pages
How you can deploy a SolidStart app to Cloudflare Pages.
As of @solidjs/[email protected]
, 7 July 2024.
This is updated as of @solidjs/[email protected]
, 2 April 2024.
pnpm create solid@latest
Configure the deployment for Cloudflare Pages
export default defineConfig({ start: { server: { preset: "cloudflare-pages", // We will need to enable CF Pages node compatiblity // https://developers.cloudflare.com/workers/runtime-apis/nodejs/asynclocalstorage/ rollupConfig: { external: ["__STATIC_CONTENT_MANIFEST", "node:async_hooks"], }, }, },})
Let’s build locally to verify the preset is being used correctly. You should see files in /dist
folder.
# Verify by running build script locally, should output the correct preset$ pnpm run build...
┌─────────────────────────────────────────────┐│ ⚙ Preparing app for cloudflare-pages... │└─────────────────────────────────────────────┘✔ Generated public distℹ Building Nitro Server (preset: cloudflare-pages)
Deploy to Cloudflare Pages
I created a repo for this on Github and linked it to a new Pages project on Cloudflare dashboard.
We need to configure these build settings
- Build command:
pnpm run build
- Build output directory:
dist

After deploying, you will notice the build fails. We need to enable node compatiblity.
- Go to Pages project Settings -> Functions -> Compatibility Flags
- Enable:
nodejs_compat

You will need to trigger a new deployment to have the changes. Visit the deployment URL and you should see the page display.
Access Cloudflare environment and bindings
Note that Cloudflare only exposes environment variables in the request
object so using process.env.SECRET
will not work.
To access Cloudflare environment and bindings, we can use getRequestEvent()
.
// Inside a server side function executionimport { getRequestEvent } from "solid-js/web"
// Server actionsexport async function addUser() { "use server" const event = getRequestEvent() event.nativeEvent.context.cloudflare.env.SECRET}
// or with a helper functionexport const env = () => { const event = getRequestEvent() // fallback to process.env for local development const env = event.nativeEvent.context.cloudflare.env ?? process.env return env}
// API routeexport async function GET(event: APIEvent) { const secret = env().SECRET}
At this point you will notice you have no types, we need to extend the context type to include the Cloudflare environment.
First install worker types.
pnpm add -D @cloudflare/workers-types
Declare your types, I do this in src/global.d.ts
.
import type { Request as CfRequest, ExecutionContext, KVNamespace, R2Bucket } from "@cloudflare/workers-types"
/** * Reference: https://developers.cloudflare.com/workers/runtime-apis/fetch-event/#parameters */export interface CfPagesEnv { ASSETS: { fetch: (request: CfRequest) => Promise<Response> } CF_PAGES: "1" CF_PAGES_BRANCH: string CF_PAGES_COMMIT_SHA: string CF_PAGES_URL: string
// Environment variables SECRET: string
// Bindings SOME_KV: KVNamespace UPLOADS_BUCKET: R2Bucket}
declare module "vinxi/http" { interface H3EventContext { cf: CfRequest["cf"] cloudflare: { request: CfRequest env: CfPagesEnv context: ExecutionContext } }}
Now event.nativeEvent.context
should have the correct types.
A pattern I found useful for accessing database or other api clients is to create a middleware that attaches the client to event.locals
.
// Returns database ORM clientexport const createDrizzleClient = () => { const client = createLibsqlClient({ url: env().DB_URL!, authToken: env().DB_TOKEN!, }) return drizzle(client)}
// Middlewareexport default createMiddleware({ onRequest: async (event) => { const ev = event.nativeEvent event.locals.db = createDrizzleClient() },})
// Extend locals typedeclare module "@solidjs/start/server" { interface RequestEventLocals { db: Db }}
You can now access the client in your server actions and API routes.
export async function GET(event: APIEvent) { const db = event.locals.db const users = await db.users.findMany() return { users }}