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.

Terminal window
pnpm create solid@latest

Configure the deployment for Cloudflare Pages

vite.config.ts
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.

Terminal window
# 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
Cloudflare Pages Build Configuration

After deploying, you will notice the build fails. We need to enable node compatiblity.

Cloudflare Pages Build Configuration

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 execution
import { getRequestEvent } from "solid-js/web"
// Server actions
export async function addUser() {
"use server"
const event = getRequestEvent()
event.nativeEvent.context.cloudflare.env.SECRET
}
// or with a helper function
export const env = () => {
const event = getRequestEvent()
// fallback to process.env for local development
const env = event.nativeEvent.context.cloudflare.env ?? process.env
return env
}
// API route
export 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.

Terminal window
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 client
export const createDrizzleClient = () => {
const client = createLibsqlClient({
url: env().DB_URL!,
authToken: env().DB_TOKEN!,
})
return drizzle(client)
}
// Middleware
export default createMiddleware({
onRequest: async (event) => {
const ev = event.nativeEvent
event.locals.db = createDrizzleClient()
},
})
// Extend locals type
declare 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 }
}