docs/pages/deployment/single-origin/vps-docker.mdx 8.9 K raw
1
# VPS / Docker
2
3
import { Button } from "vocs/components";
4
5
Serve both your frontend and API from the same process, same port, and same originβ€”ideal for fullstack apps where simplicity matters.
6
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
9
Perfect for:
10
11
- VPS deployments
12
- Raspberry Pis
13
- Home servers
14
- Docker containers
15
- Projects where you want one URL to rule them all
16
17
## Prerequisites
18
19
This guide assumes you have a bhvr project set up. If not, start here:
20
21
```bash
22
bun create bhvr@latest my-app
23
cd my-app
24
```
25
26
<Button href='/getting-started'>Getting Started with bhvr</Button>
27
28
## What Is Single Origin?
29
30
Instead of running your client and server separately (the default bhvr setup), single origin serves everything from one process:
31
32
**Default bhvr setup:**
33
34
- Client runs on port 5173 (Vite dev server)
35
- Server runs on port 3000 (Hono API)
36
- Requires CORS for communication
37
38
**Single origin setup:**
39
40
- Everything runs on port 3000
41
- Hono serves both API routes and static React files
42
- No CORS needed
43
44
## Configuration
45
46
### 1. Update Your Hono Server
47
48
Modify `server/src/index.ts` to serve static files alongside your API:
49
50
```typescript
51
import { Hono } from "hono";
52
import { cors } from "hono/cors";
53
import { serveStatic } from "hono/bun"; // [!code ++]
54
import type { ApiResponse } from "shared/dist";
55
56
const app = new Hono();
57
58
// CORS is optional for single origin deployment
59
// Keep for development flexibility, remove for production if desired
60
app.use(cors());
61
62
// Your existing API routes - keep the /api prefix for clarity
63
app.get("/api", (c) => {
64
  return c.text("Hello Hono!");
65
});
66
67
app.get("/api/hello", async (c) => {
68
  const data: ApiResponse = {
69
    message: "Hello BHVR!",
70
    success: true,
71
  };
72
  return c.json(data, { status: 200 });
73
});
74
75
// Add more API routes here with /api prefix
76
// app.get('/api/users', ...)
77
// app.post('/api/data', ...)
78
79
// Serve static files for everything else
80
app.use("*", serveStatic({ root: "./static" })); // [!code ++]
81
82
app.get("*", async (c, next) => { // [!code ++]
83
  return serveStatic({ root: "./static", path: "index.html" })(c, next); // [!code ++]
84
}); // [!code ++]
85
86
const port = parseInt(process.env.PORT || "3000");
87
88
export default {
89
  port,
90
  fetch: app.fetch,
91
};
92
93
console.log(`🦫 bhvr server running on port ${port}`);
94
```
95
96
### 2. Update Your React Client
97
98
Modify `client/src/App.tsx` to use relative API paths:
99
100
```typescript
101
import { useState } from "react";
102
import beaver from "./assets/beaver.svg";
103
import { ApiResponse } from "shared";
104
import "./App.css";
105
106
function App() {
107
  const [data, setData] = useState<ApiResponse | undefined>();
108
109
  async function sendRequest() {
110
    try {
111
      // Use relative path - works in both dev and production
112
      const req = await fetch("/api/hello"); // [!code hl]
113
      const res: ApiResponse = await req.json();
114
      setData(res);
115
    } catch (error) {
116
      console.log(error);
117
    }
118
  }
119
120
  return (
121
    <>
122
      <div>
123
        <a href='https://github.com/stevedylandev/bhvr' target='_blank'>
124
          <img src={beaver} className='logo' alt='beaver logo' />
125
        </a>
126
      </div>
127
      <h1>bhvr</h1>
128
      <h2>Bun + Hono + Vite + React</h2>
129
      <p>A typesafe fullstack monorepo</p>
130
      <div className='card'>
131
        <button onClick={sendRequest}>Call API</button>
132
        {data && (
133
          <pre className='response'>
134
            <code>
135
              Message: {data.message} <br />
136
              Success: {data.success.toString()}
137
            </code>
138
          </pre>
139
        )}
140
      </div>
141
      <p className='read-the-docs'>Click the beaver to learn more</p>
142
    </>
143
  );
144
}
145
146
export default App;
147
```
148
149
### 3. Configure Vite for Development
150
151
Update `client/vite.config.ts` to proxy API calls during development:
152
153
```typescript
154
import { defineConfig } from "vite";
155
import react from "@vitejs/plugin-react";
156
import path from "path";
157
158
export default defineConfig({
159
  plugins: [react()],
160
  resolve: {
161
    alias: {
162
      "@client": path.resolve(__dirname, "./src"),
163
      "@server": path.resolve(__dirname, "../server/src"),
164
      "@shared": path.resolve(__dirname, "../shared/src"),
165
    },
166
  },
167
  server: { // [!code ++]
168
    proxy: { // [!code ++]
169
      "/api": { // [!code ++]
170
        target: "http://localhost:3000", // [!code ++]
171
        changeOrigin: true, // [!code ++]
172
      }, // [!code ++]
173
    }, // [!code ++]
174
  }, // [!code ++]
175
});
176
```
177
178
### 4. Add Single Origin Scripts
179
180
Add these scripts to your root `package.json` (alongside the existing bhvr scripts):
181
182
```json
183
{
184
  "scripts": {
185
    "dev": "turbo dev",
186
    "dev:client": "turbo dev --filter=client",
187
    "dev:server": "turbo dev --filter=server",
188
    "build": "turbo build",
189
    "build:client": "turbo build --filter=client",
190
    "build:server": "turbo build --filter=server",
191
    "build:single": "bun run build && bun run copy:static && bun run build:server", // [!code ++]
192
    "copy:static": "rm -rf server/static && cp -r client/dist server/static", // [!code ++]
193
    "start:single": "cd server && bun run dist/index.js", // [!code ++]
194
    "lint": "turbo lint",
195
    "type-check": "turbo type-check",
196
    "test": "turbo test",
197
    "postinstall": "turbo build --filter=shared --filter=server"
198
  }
199
}
200
```
201
202
## Development vs Production
203
204
### Development (Default bhvr)
205
206
Use the standard bhvr development workflow:
207
208
```bash
209
bun run dev
210
```
211
212
This runs:
213
214
- Client on `http://localhost:5173` (Vite dev server)
215
- Server on `http://localhost:3000` (Hono API)
216
- Vite proxy forwards `/api` calls to the server
217
218
### Production (Single Origin)
219
220
Build and run from single origin:
221
222
```bash
223
# Build everything and prepare for single origin
224
bun run build:single
225
226
# Start the single origin server
227
bun run start:single
228
```
229
230
Your app now runs entirely on `http://localhost:3000`.
231
232
## Deployment
233
234
### Docker
235
236
```dockerfile
237
FROM oven/bun:latest
238
WORKDIR /app
239
240
# Copy package files
241
COPY package.json bun.lock ./
242
COPY client/package.json ./client/
243
COPY server/package.json ./server/
244
COPY shared/package.json ./shared/
245
246
# Copy source code
247
COPY . .
248
249
# Install dependencies
250
RUN bun install
251
252
# Build for single origin
253
RUN bun run build:single
254
255
EXPOSE 3000
256
CMD ["bun", "run", "start:single"]
257
```
258
259
### VPS / Bare Metal
260
261
```bash
262
# Clone your bhvr project
263
git clone <your-repo> my-app && cd my-app
264
265
# Install and build
266
bun install
267
bun run build:single
268
269
# Run (consider using PM2 or systemd for production)
270
bun run start:single
271
```
272
273
### Environment Variables
274
275
Configure the port and other settings:
276
277
```bash
278
PORT=8080 bun run start:single
279
```
280
281
## File Structure
282
283
After building for single origin, your bhvr project structure looks like:
284
285
```
286
.
287
β”œβ”€β”€ client/
288
β”‚   β”œβ”€β”€ dist/           # Built React app
289
β”‚   └── src/
290
β”œβ”€β”€ server/
291
β”‚   β”œβ”€β”€ dist/
292
β”‚   β”‚   └── index.js    # Built Hono server
293
β”‚   β”œβ”€β”€ static/         # Copied from client/dist
294
β”‚   └── src/
295
β”œβ”€β”€ shared/
296
β”‚   β”œβ”€β”€ dist/           # Built shared types
297
β”‚   └── src/
298
└── package.json
299
```
300
301
## CORS Configuration
302
303
Since single origin serves everything from the same origin, **CORS is not required** for production. However, you might want to keep it for development flexibility:
304
305
**Production (Single Origin):**
306
307
- React app and API served from same origin (e.g., `https://yourapp.com`)
308
- All requests are same-origin
309
- CORS not needed
310
311
**Development:**
312
313
- Vite proxy handles cross-origin requests automatically
314
- CORS still optional due to proxy, but useful for:
315
  - Testing API directly in browser/tools
316
  - Alternative development setups
317
  - Third-party integrations during development
318
319
**To remove CORS for production**, you can conditionally apply it:
320
321
```typescript
322
// Only use CORS in development
323
if (process.env.NODE_ENV !== "production") {
324
  app.use(cors());
325
}
326
```
327
328
Or remove the `app.use(cors())` line entirely if you don't need development flexibility.
329
330
## Key Benefits
331
332
- **Simplified deployment**: One process, one port, one URL
333
- **No CORS complexity**: Frontend and API share the same origin
334
- **Maintains bhvr workflow**: Still use `bun run dev` for development
335
- **Type safety preserved**: All bhvr type sharing continues to work
336
- **Resource efficient**: Perfect for small VPS, Raspberry Pi, or containers
337
338
## Troubleshooting
339
340
**API calls fail in development?**
341
342
- Ensure Vite proxy is configured in `client/vite.config.ts`
343
- Check that your server is running on port 3000
344
345
**404 errors on page refresh?**
346
347
- The `serveStatic` catchall should handle SPA routing automatically
348
- Verify client files are copied to `server/static/`
349
350
**Build fails?**
351
352
- Run `bun run build` first to ensure shared types are available
353
- Check that all bhvr workspaces install correctly
354
355
## Summary
356
357
Single origin deployment transforms your bhvr project from a multi-port development setup into a production-ready single process application, while preserving all the type safety and development experience that makes bhvr great.
358
359
## More Resources
360
361
<Button href='/getting-started'>Getting Started with bhvr</Button>