A minimal Remix 3 starter for Cloudflare Workers.
npm install
# Start vite dev server with the Cloudflare Vite plugin
npm run dev
# Preview the production build locally
npm run preview
# Deploy to Cloudflare
npm run deployThis template replaces the default Remix Assets Server with a Vite-based setup that runs in the Cloudflare Workers runtime and produces the client build for deployment.
If you are comparing this template with the official Remix template, or migrating an app from that template to Cloudflare, here are the main changes.
Update ./server.ts to export a fetch() handler:
import { router } from './app/router.ts'
export default {
async fetch(request: Request) {
try {
return await router.fetch(request)
} catch (error) {
console.error(error)
return new Response('Internal Server Error', { status: 500 })
}
},
}Then create a wrangler.jsonc with main: "./server.ts".
Move the browser entry to ./client.ts, then resolve app modules through import.meta.glob(...). Vite automatically includes those client modules in both dev and production builds.
import { run } from 'remix/ui'
const clientModules = import.meta.glob(['/app/**/*.{ts,tsx}', '!/app/**/*.server.*'])
run({
async loadModule(moduleUrl, exportName) {
let load = clientModules[moduleUrl]
if (!load) {
throw new Error(`Unknown client entry module: ${moduleUrl}`)
}
let mod = await load()
if (!mod || typeof mod !== 'object') {
throw new Error(`Invalid client entry module: ${moduleUrl}`)
}
let entry = Reflect.get(mod, exportName)
if (typeof entry !== 'function') {
throw new Error(`Missing client entry export ${exportName} in ${moduleUrl}`)
}
return entry
},
async resolveFrame(src, signal, target) {
// ... no change here
},
})Add a vite.config.ts with @cloudflare/vite-plugin, then tell Vite to build client.ts to assets/client.js:
export default defineConfig({
environments: {
client: {
build: {
rollupOptions: {
input: fileURLToPath(new URL('./client.ts', import.meta.url)),
output: {
entryFileNames: 'assets/client.js',
chunkFileNames: 'assets/[name]-[hash].js',
},
},
},
},
},
plugins: [cloudflare()],
})Make sure the document script points to /client.ts in dev and /assets/client.js in production:
const CLIENT_ENTRY_SRC = import.meta.env.DEV ? '/client.ts' : '/assets/client.js'
<script type="module" src={CLIENT_ENTRY_SRC}></script>