Cloudflare
By using Cloudflare Workers you can host the server
and the client
in one deployment thanks to the built in Static Assets Feature. With this approach everything will come through a single origin where any routes not covered by Hono will fallback to resolving via the static assets folder, which in our case is our React app.
Prerequisites
This guide assumes you have a bhvr project set up. If not, start here:
bun create bhvr@latest my-app
cd my-app
Configuration
1. Update Your Hono Server
Modify server/src/index.ts
to have a new basepath of /api
import { Hono } from "hono";
import { cors } from "hono/cors";
import type { ApiResponse } from "shared/dist";
const app = new Hono()
const app = new Hono().basePath("/api");
app.use(cors());
app.get("/", (c) => {
return c.text("Hello Hono!");
});
app.get("/hello", async (c) => {
const data: ApiResponse = {
message: "Hello BHVR!",
success: true,
};
return c.json(data, { status: 200 });
});
export default app;
2. Update Your React Client
Modify client/src/App.tsx
to use a dynamic SERVER_URL
based on dev / prod environment
import { useState } from "react";
import beaver from "./assets/beaver.svg";
import { ApiResponse } from "shared";
import "./App.css";
const SERVER_URL = import.meta.env.VITE_SERVER_URL || "http://localhost:3000"
const SERVER_URL = import.meta.env.DEV ? "http://localhost:3000/api" : "/api";
function App() {
const [data, setData] = useState<ApiResponse | undefined>();
async function sendRequest() {
try {
const req = await fetch(`${SERVER_URL}/hello`);
const res: ApiResponse = await req.json();
setData(res);
} catch (error) {
console.log(error);
}
}
return (
<>
<div>
<a href='https://github.com/stevedylandev/bhvr' target='_blank'>
<img src={beaver} className='logo' alt='beaver logo' />
</a>
</div>
<h1>bhvr</h1>
<h2>Bun + Hono + Vite + React</h2>
<p>A typesafe fullstack monorepo</p>
<div className='card'>
<button onClick={sendRequest}>Call API</button>
{data && (
<pre className='response'>
<code>
Message: {data.message} <br />
Success: {data.success.toString()}
</code>
</pre>
)}
</div>
<p className='read-the-docs'>Click the beaver to learn more</p>
</>
);
}
export default App;
3. Setup Wrangler
Install wrangler
and it's types at the root of your bhvr project
bun add --dev wrangler @cloudflare/workers-types
Then create another file at the root of the project called wrangler.jsonc
with the following content:
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "bhvr-project", // Name of your project
"main": "./server/dist/index.js", // Path to worker
"compatibility_date": "2025-05-25",
"assets": {
"directory": "./client/dist", // Path to client build folder
"not_found_handling": "single-page-application" // Handle SPA routing
},
"compatibility_flags": ["nodejs_compat"] // Enable node for Vite path features
}
4. Add Deploy Script
Append a deploy script to your root package.json
(alongside the existing bhvr scripts):
{
"scripts": {
"dev": "turbo dev",
"dev:client": "turbo dev --filter=client",
"dev:server": "turbo dev --filter=server",
"build": "turbo build",
"build:client": "turbo build --filter=client",
"build:server": "turbo build --filter=server",
"lint": "turbo lint",
"type-check": "turbo type-check",
"test": "turbo test",
"postinstall": "turbo build --filter=shared --filter=server",
"deploy": "turbo build && wrangler deploy --minify"
},
}
5. Deploy
Make sure you have logged into Cloudflare using Wrangler first
bunx wrangler login
Then run the deployment script
bun run deploy
Environment Variables
You can use environment variables just like you would with Hono + Cloudflare workers as described in the Hono Docs for Bindings. Here is an example of what you might have in server/src/index.ts
:
import { Hono } from "hono";
import { cors } from "hono/cors";
import type { ApiResponse } from "shared/dist";
type Bindings = {
SECRET: string;
};
const app = new Hono<{ Bindings: Bindings }>().basePath("/api");
app.use(cors());
app.get("/", (c) => {
return c.text("Hello Hono!");
});
app.get("/hello", async (c) => {
const data: ApiResponse = {
message: `Hello BHVR! (this is the secret: ${c.env.SECRET}`,
success: true,
};
return c.json(data, { status: 200 });
});
export default app;
To add the secret in dev you would create a .dev.vars
file in server
with the variable
SECRET=hotdog
To add it in production, you can either add it through the Cloudflare dashboard or through Wrangler:
bunx wrangler secret put SECRET
# Will prompt you to enter the secret
For client side variables you can simply include them in a local .env.local
file in the root of the client
package, and make sure to use the VITE_
prefix for them. When you build they will automatically be included in the dist
bundle.