Web
Build web applications with Bini.js - the default platform target.
Web Overview
Web is the default platform target in Bini.js. It is a standard Vite + React SPA with file-based routing, pre-rendering support, and a Hono API layer. Your application runs in the browser and can be deployed to any hosting platform.
Single-page application with client-side routing
Hono-powered API routes in src/app/api/
Static HTML with bini-ssg
--platform flag required.Requirements
Web apps are pure JavaScript/TypeScript, so no native toolchain is needed. The only prerequisites are Node.js and Git - they work the same on every OS.
- Node.js 20.19.0 or higher
- Git - comes with Xcode Command Line Tools on macOS,
sudo apt install giton Linux, andwinget install --id Git.Giton Windows
Windows
On Windows you can scaffold, develop, and build the app entirely on your own machine - no CI required. Web apps are pure JavaScript/TypeScript, so no native toolchain is needed.
Step 1: Install the tools
- Node.js 20.19.0 or higher
- Git for Windows -
winget install --id Git.Gitor download fromgit-scm.com
Step 2: Create the project
Create the Project
Create a new Bini.js project targeting Web and install its dependencies:
$ npx create-bini-app@latest my-app --platform web $ cd my-app $ npm install
Or use the interactive prompt and select Web Application:
$ npx create-bini-app@latest
? Select target platform:> Web ApplicationWindows DesktopLinux DesktopmacOS DesktopAndroidiOS↑↓ navigate • ⏎ select
Step 3: Develop
Run dev to start the Vite dev server with HMR:
$ npm run dev
--platform web is optional.macOS
On macOS you can scaffold, develop, and build the app entirely on your own machine - no CI required. Web apps are pure JavaScript/TypeScript, so no native toolchain is needed.
Step 1: Install the tools
- Node.js 20.19.0 or higher
- Git - comes with Xcode Command Line Tools. Run
xcode-select --installif you do not have it yet.
Step 2: Create the project
Create the Project
Create a new Bini.js project targeting Web and install its dependencies:
$ npx create-bini-app@latest my-app --platform web $ cd my-app $ npm install
Or use the interactive prompt and select Web Application:
$ npx create-bini-app@latest
? Select target platform:> Web ApplicationWindows DesktopLinux DesktopmacOS DesktopAndroidiOS↑↓ navigate • ⏎ select
Step 3: Develop
Run dev to start the Vite dev server with HMR:
$ npm run dev
--platform web is optional.Linux
On Linux you can scaffold, develop, and build the app entirely on your own machine - no CI required. Web apps are pure JavaScript/TypeScript, so no native toolchain is needed.
Step 1: Install the tools
- Node.js 20.19.0 or higher
- Git with your package manager, for example
sudo apt install giton Debian and Ubuntu.
Step 2: Create the project
Create the Project
Create a new Bini.js project targeting Web and install its dependencies:
$ npx create-bini-app@latest my-app --platform web $ cd my-app $ npm install
Or use the interactive prompt and select Web Application:
$ npx create-bini-app@latest
? Select target platform:> Web ApplicationWindows DesktopLinux DesktopmacOS DesktopAndroidiOS↑↓ navigate • ⏎ select
Step 3: Develop
Run dev to start the Vite dev server with HMR:
$ npm run dev
--platform web is optional.Development Server
Start the development server with HMR (Hot Module Replacement). This command is the same on every OS:
$ npm run dev
The dev server provides:
- Fast refresh with HMR
- File-based routing with live updates
- API routes served at
/api/* - Environment variables from
.envfiles - Error overlay with
bini-overlay
Production Server
Build and serve your application in production mode:
$ npm run build $ npm start
bini-server is a zero-dependency production server that includes:
- Static file serving with ETag/304 caching
- API routes from
src/app/api/ - SPA fallback for client-side routing
- Graceful shutdown
- Configurable timeouts and body limits
Pre-rendering
Every route is pre-rendered to static HTML during npm run build. There is no separate export command or export mode - bini-ssg drives pre-rendering as part of the same build.
How It Works
npm run build type-checks (TypeScript projects) and then runs vite build. The bini-ssg plugin drives pre-rendering as part of that same build:
- Static routes (e.g.,
/,/about) are rendered to real server-rendered HTML - Dynamic routes (e.g.,
/blog/:slug) get a shell page with hydration - React 19
renderToPipeableStreamis used for server rendering - StaticRouter from React Router provides the routing context
Your render() Function
The render() function is exported from src/main.tsx:
// src/main.tsx
import { createRoot } from 'react-dom/client'
import App from './App'
// Client mount
createRoot(document.getElementById('root')!).render(<App />)
// SSG render (called by bini-ssg, Node-only)
export async function render(url: string): Promise<string> {
const { renderToString } = await import('react-dom/server')
const { StaticRouter } = await import('react-router-dom/server')
const { AppRoutes } = await import('./App')
return renderToString(
<StaticRouter location={url}>
<AppRoutes />
</StaticRouter>
)
}Build Command
$ npm run build
The output is real server-rendered markup, not a client-only shell. The client then hydrates it with hydrateRoot on load.
Output Structure
Each route gets its own index.html in dist/:
- Highlighted files are fully pre-rendered pages for
/and/about /blog/:slugand/docs/*are shell pagesjs/andcss/hold your compiled JavaScript and CSS files
Hydration and Shell Pages
For dynamic routes, bini-ssg injects a marker script:
<!-- injected by bini-ssg -->
<script>window.__BINI_SHELL__=true;</script>Your client entry checks this flag:
// src/main.tsx
import { createRoot, hydrateRoot } from 'react-dom/client'
import App from './App'
declare global {
interface Window {
__BINI_SHELL__?: boolean
}
}
const root = document.getElementById('root')!
if (window.__BINI_SHELL__) {
createRoot(root).render(<App />)
} else {
hydrateRoot(root, <App />)
}Deployment
bini-deploy is bundled into every scaffold and exposed as npm run deploy. For web, it prompts for a hosting target and generates the appropriate configuration.
Deploy Command
$ npm run deploy
Generated Files
The target you choose determines what bini-deploy creates:
| Platform | Runtime | File Generated |
|---|---|---|
| Node.js | Node.js | - (bini-server reads src/app/api/ directly) |
| Netlify | Edge Functions (Deno) | netlify/edge-functions/api.ts + netlify.toml |
| Vercel | Edge Runtime | api/index.ts + vercel.json |
| Cloudflare | Workers | worker.ts + wrangler.toml |
| Deno | Deno | server/index.ts |
Deployment Options
- SPA + API Server: Build with
npm run build, deploy withnpm start(requires Node.js) - Pre-rendered Static: Build with
npm run build, deploy thedist/folder to any static hosting - Edge/Serverless: Use
npm run deployto generate platform-specific entry files
Node.js Deployment
For Node.js hosts (Railway, Render, Fly.io, a VPS):
$ npm run build && npm start
bini-server reads handlers directly from src/app/api/, so deploy the whole project - not just dist/. Use pm2 on a bare VPS.
GitHub Pages / Subpaths
Set base: '/my-repo/' in vite.config.ts, then npm run build for a fully pre-rendered, subpath-aware dist/.
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { biniroute } from 'bini-router'
export default defineConfig({
base: '/my-repo/', // GitHub Pages subpath
plugins: [react(), biniroute()],
})