|
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> |