feat: Adds Cloudlfare Single Origin Deployment 7fb8fdad
Per https://github.com/stevedylandev/bhvr/issues/26 this new doc shows
how you can deploy the client and server under a single Cloudlfare
worker, eliminating the need for a Docker or container setup
Steve Simkins · 2025-08-01 07:19 4 file(s) · +243 −3
docs/pages/deployment/single-origin.mdx → docs/pages/deployment/single-origin/vps-docker.mdx +2 −2
1 -
# Single Origin Deployment
1 +
# VPS / Docker
2 2
3 3
import { Button } from "vocs/components";
4 4
5 5
Serve both your frontend and API from the same process, same port, and same origin—ideal for fullstack apps where simplicity matters.
6 6
7 -
This guide walks through configuring your bhvr project for **single origin** deployment, where your React frontend and Hono API run from the same Bun process.
7 +
This guide walks through configuring your bhvr project for single origin deployment on a VPS , where your React frontend and Hono API run from the same Bun process.
8 8
9 9
Perfect for:
10 10
docs/pages/deployment/single-origin/cloudflare.mdx (added) +229 −0
1 +
# Cloudflare
2 +
3 +
import { Button } from "vocs/components";
4 +
5 +
By using Cloudflare Workers you can host the `server` and the `client` in one deployment thanks to the built in [Static Assets Feature](https://hono.dev/docs/getting-started/cloudflare-workers#serve-static-files). 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.
6 +
7 +
## Prerequisites
8 +
9 +
This guide assumes you have a bhvr project set up. If not, start here:
10 +
11 +
```bash
12 +
bun create bhvr@latest my-app
13 +
cd my-app
14 +
```
15 +
16 +
<Button href='/getting-started'>Getting Started with bhvr</Button>
17 +
18 +
## Configuration
19 +
20 +
::::steps
21 +
22 +
### 1. Update Your Hono Server
23 +
24 +
Modify `server/src/index.ts` to have a new basepath of `/api`
25 +
26 +
```typescript
27 +
import { Hono } from "hono";
28 +
import { cors } from "hono/cors";
29 +
import type { ApiResponse } from "shared/dist";
30 +
31 +
const app = new Hono() // [!code --]
32 +
const app = new Hono().basePath("/api"); // [!code ++]
33 +
34 +
app.use(cors());
35 +
36 +
app.get("/", (c) => {
37 +
	return c.text("Hello Hono!");
38 +
});
39 +
40 +
app.get("/hello", async (c) => {
41 +
	const data: ApiResponse = {
42 +
		message: "Hello BHVR!",
43 +
		success: true,
44 +
	};
45 +
46 +
	return c.json(data, { status: 200 });
47 +
});
48 +
49 +
export default app;
50 +
```
51 +
52 +
### 2. Update Your React Client
53 +
54 +
Modify `client/src/App.tsx` to use a dynamic `SERVER_URL` based on dev / prod environment
55 +
56 +
```typescript
57 +
import { useState } from "react";
58 +
import beaver from "./assets/beaver.svg";
59 +
import { ApiResponse } from "shared";
60 +
import "./App.css";
61 +
62 +
const SERVER_URL = import.meta.env.VITE_SERVER_URL || "http://localhost:3000" // [!code --]
63 +
const SERVER_URL = import.meta.env.DEV ? "http://localhost:3000/api" : "/api";  // [!code ++]
64 +
65 +
function App() {
66 +
  const [data, setData] = useState<ApiResponse | undefined>();
67 +
68 +
  async function sendRequest() {
69 +
    try {
70 +
      const req = await fetch(`${SERVER_URL}/hello`);
71 +
      const res: ApiResponse = await req.json();
72 +
      setData(res);
73 +
    } catch (error) {
74 +
      console.log(error);
75 +
    }
76 +
  }
77 +
78 +
  return (
79 +
    <>
80 +
      <div>
81 +
        <a href='https://github.com/stevedylandev/bhvr' target='_blank'>
82 +
          <img src={beaver} className='logo' alt='beaver logo' />
83 +
        </a>
84 +
      </div>
85 +
      <h1>bhvr</h1>
86 +
      <h2>Bun + Hono + Vite + React</h2>
87 +
      <p>A typesafe fullstack monorepo</p>
88 +
      <div className='card'>
89 +
        <button onClick={sendRequest}>Call API</button>
90 +
        {data && (
91 +
          <pre className='response'>
92 +
            <code>
93 +
              Message: {data.message} <br />
94 +
              Success: {data.success.toString()}
95 +
            </code>
96 +
          </pre>
97 +
        )}
98 +
      </div>
99 +
      <p className='read-the-docs'>Click the beaver to learn more</p>
100 +
    </>
101 +
  );
102 +
}
103 +
104 +
export default App;
105 +
```
106 +
107 +
### 3. Setup Wrangler
108 +
109 +
Install `wrangler` and it's types at the root of your bhvr project
110 +
111 +
```bash [terminal]
112 +
bun add --dev wrangler @cloudflare/workers-types
113 +
```
114 +
115 +
Then create another file at the root of the project called `wrangler.jsonc` with the following content:
116 +
117 +
```jsonc
118 +
{
119 +
	"$schema": "./node_modules/wrangler/config-schema.json",
120 +
	"name": "bhvr-project", // Name of your project
121 +
	"main": "./server/dist/index.js", // Path to worker
122 +
	"compatibility_date": "2025-05-25",
123 +
	"assets": {
124 +
		"directory": "./client/dist", // Path to client build folder
125 +
		"not_found_handling": "single-page-application" // Handle SPA routing
126 +
	},
127 +
	"compatibility_flags": ["nodejs_compat"] // Enable node for Vite path features
128 +
}
129 +
```
130 +
131 +
### 4. Add Deploy Script
132 +
133 +
Append a deploy script to your root `package.json` (alongside the existing bhvr scripts):
134 +
135 +
```json
136 +
{
137 +
  "scripts": {
138 +
		"dev": "turbo dev",
139 +
		"dev:client": "turbo dev --filter=client",
140 +
		"dev:server": "turbo dev --filter=server",
141 +
		"build": "turbo build",
142 +
		"build:client": "turbo build --filter=client",
143 +
		"build:server": "turbo build --filter=server",
144 +
		"lint": "turbo lint",
145 +
		"type-check": "turbo type-check",
146 +
		"test": "turbo test",
147 +
		"postinstall": "turbo build --filter=shared --filter=server",
148 +
		"deploy": "turbo build && wrangler deploy --minify" // [!code ++]
149 +
	},
150 +
}
151 +
```
152 +
153 +
### 5. Deploy
154 +
155 +
Make sure you have logged into Cloudflare using Wrangler first
156 +
157 +
```bash [terminal]
158 +
bunx wrangler login
159 +
```
160 +
161 +
Then run the deployment script
162 +
163 +
```bash [terminal]
164 +
bun run deploy
165 +
```
166 +
167 +
::::
168 +
169 +
## Environment Variables
170 +
171 +
You can use environment variables just like you would with Hono + Cloudflare workers as described in the [Hono Docs](https://hono.dev/docs/getting-started/cloudflare-workers#bindings) for Bindings. Here is an example of what you might have in `server/src/index.ts`:
172 +
173 +
```typescript
174 +
import { Hono } from "hono";
175 +
import { cors } from "hono/cors";
176 +
import type { ApiResponse } from "shared/dist";
177 +
178 +
type Bindings = {
179 +
	SECRET: string;
180 +
};
181 +
182 +
const app = new Hono<{ Bindings: Bindings }>().basePath("/api");
183 +
184 +
app.use(cors());
185 +
186 +
app.get("/", (c) => {
187 +
	return c.text("Hello Hono!");
188 +
});
189 +
190 +
app.get("/hello", async (c) => {
191 +
	const data: ApiResponse = {
192 +
		message: `Hello BHVR! (this is the secret: ${c.env.SECRET}`,
193 +
		success: true,
194 +
	};
195 +
196 +
	return c.json(data, { status: 200 });
197 +
});
198 +
199 +
export default app;
200 +
```
201 +
202 +
To add the secret in dev you would create a `.dev.vars` file in `server` with the variable
203 +
204 +
```
205 +
SECRET=hotdog
206 +
```
207 +
208 +
:::warning
209 +
Make sure to add .dev.vars to your `.gitignore`!
210 +
:::
211 +
212 +
To add it in production, you can either add it through the Cloudflare dashboard or through Wrangler:
213 +
214 +
```bash [terminal]
215 +
bunx wrangler secret put SECRET
216 +
# Will prompt you to enter the secret
217 +
```
218 +
219 +
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.
220 +
221 +
:::warning
222 +
Make sure only public variables are in the client!
223 +
:::
224 +
225 +
226 +
## More Resources
227 +
228 +
<Button href='/getting-started'>Getting Started with bhvr</Button>
229 +
<Button href='https://hono.dev'>Hono Docs</Button>
docs/public/_redirects (added) +1 −0
1 +
/deployment/single-origin   /deployment/single-origin/cloudflare
vocs.config.tsx +11 −1
146 146
				},
147 147
				{
148 148
					text: "Single Origin",
149 -
					link: "/deployment/single-origin",
149 +
					collapsed: true,
150 +
					items: [
151 +
						{
152 +
							text: "Cloudflare",
153 +
							link: "/deployment/single-origin/cloudflare",
154 +
						},
155 +
						{
156 +
							text: "VPS / Docker",
157 +
							link: "/deployment/single-origin/vps-docker",
158 +
						},
159 +
					],
150 160
				},
151 161
			],
152 162
		},