Cloudflare as a framework

By VinayMay 5, 2025

Intro

If you’ve written an even moderately complex web application you know the pain of deploying it on aws or on pretty much any provider if you may ask. Most of us tend to use vercel or netlify because it eases our life so much, so wouldn’t it be great if we could ease the deployment a bit and and keep the power of aws at our fingertips too?

Behold Cloudflare! Cloudflare has been doing some of the great work of in the past few years. Not only they keep adding more & more services to their platform but they also increased the compatibility layer between workers and nodejs api enormously, which means you can migrate your existing application on cloudflare very easily, you don’t have to dig through docs to find that one specific api etc. Cloudflare not only provides you services like serverless functions, d1 database, R2 bucket, Image transformation, bullet proof DNS but all of them are dirt cheap with very generous free tier. So in this blog we explore how to deploy an application to cloudflare.

Where to start

If you’ve never tried cloudflare before i would suggest you to checkout cloudflare pages which is very easy to setup and you’ll feel right at home if you’ve used vercel or netlify. For more advance use case you can use their cli tool which sets up everything for you according to your preference eg. which framework you want to use, you can hop in to the terminal and type pnpm create cloudflare@latest; press enter, select your framework and sit back until it asks you if you want to deploy right away. its that easy!

The end. Thanks for reading.

Nah just kidding. There’s a lot of times you don’t want to use a meta framework and just need to use like a regular express like backend with your own choice of frontend framework, and it could be possible that cloudflare might not support it fully yet. This is where you need to set up manually and this article is for that niche audience.

Let’s Begin

We start by creating a basic directory structure using vite. It’s almost the industry standard for frontend world.

pnpm create vite@latest

note: It does not matter what you choose, its all static assets

You’ll have something like this

├── eslint.config.js
├── index.html
├── package.json
├── public
│   └── vite.svg
├── readme.md
├── src
│   ├── app.css
│   ├── app.tsx
│   ├── assets
│   │   └── react.svg
│   ├── index.css
│   ├── main.tsx
│   └── vite-env.d.ts
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts

Install dependencies. your fronend is done.

Now let’s add workers to it. Run

pnpm install wrangler @cloudflare/vite-plugin @types/node

Let’s see what each of them do:

First open your vite.config.ts and add the cloudflare plugin

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { cloudflare } from '@cloudflare/vite-plugin'

// https://vite.dev/config/
export default defineConfig({
  plugins: [react(), cloudflare()],
})

Now create wrangler.json in project root directory and paste this:

{
  "name": "handroll-worker",
  "compatibility_date": "2025-04-05",
  "assets": {
    "not_found_handling": "single-page-application"
  },
  "observability": {
    "enabled": true
  },
  "main": "./workerapi/index.ts"
}

Now run, pnpm wrangler types to generate cloudflare types so we can add it to the tsconfig. It will generate a worker-configuration.d.ts file

Now create tsconfig.worker.json and add this to the file

 {
  "extends": "./tsconfig.node.json",
  "compilerOptions": {
    "types": [
      "vite/client"
    ]
  },
  "include": [
    "./worker-configuration.d.ts",
    "./workerapi"
  ]
}

Also open tsconfig.json and add this worker config to the path. your tsconfig.json should look something like this.

{
  "files": [],
  "references": [
    {
      "path": "./tsconfig.app.json"
    },
    {
      "path": "./tsconfig.node.json"
    },
    {
      "path": "./tsconfig.worker.json"
    }
  ],
  "compilerOptions": {
    "types": [
      "worker-configuration.d.ts"
    ]
  }
}

Now create index file in workerapi. Open workerapi/index.ts and add the following content.

export default {
  fetch(request) {
    const url = new URL(request.url);

    if (url.pathname.startsWith("/api/")) {
      return Response.json({
        name: "Cloudflare",
      });
    }
    return new Response(null, { status: 404 });
  },
} satisfies ExportedHandler<Env>;

This defines an “/api” endpoint that returns a json object. We can now call this on our frontend. Create a useState variable:

const [name, setName] = useState('empty')

Then add the fetch handler to get the data from the backend:

<div className='card'>
  <button
    onClick={() => {
      fetch('/api')
        .then((res) => res.json() as Promise<{ name: string }>)
        .then((data) => setName(data.name))
    }}
    aria-label='get name'
  >
    Name from API is: {name}
  </button>
</div>

You can now run pnpm run build && pnpm wrangler deploy and it will deploy the worker within seconds.

Cloudflare + Vite

Technically we’re done here but who likes to create endpoints from scratch like this.

if (url.pathname.startsWith("/api/")) {
  return Response.json({
    name: "Cloudflare",
  });
}

So we’ll add Hono to our worker. Hono is a great express like backend which has first class support for cloudflare workers. Here’s how to do it.

Install Hono using your favourite package manager.

Create a new file in the directory ‘workerapi/hono/index.ts’ create a basic api endpoint:

import { Hono } from "hono";

export const app = new Hono()
  .get('/api', (c) => {
      return c.json({ name: 'Cloudflare' })
  })

Now go to workersapi/index.ts and change its content to:

import { app } from "./hono";

export default {
  fetch(request, env, ctx) {
    return app.fetch(request, env, ctx)
  },
} satisfies ExportedHandler<Env>;

So what is happening here?

We are creating a handler using Hono and exporting it to the workers with the env variables and contexts.

Now you can fire up development server and you should see things working as expected.

Wrap up

So what are the advantages of this setup?

We can do a lot more than this but seems like its enough for today. we’ll explore more in next article.