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.

SPA

Single-page application with client-side routing

API Layer

Hono-powered API routes in src/app/api/

Pre-rendering

Static HTML with bini-ssg

Web is the default platform on every OS - no --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 git on Linux, and winget install --id Git.Git on Windows
You do not need Rust, Xcode, MSVC, or any platform-specific SDK to build a web app. Those are only required when targeting native desktop or mobile platforms.

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.Git or download from git-scm.com

Step 2: Create the project

Create the Project

Create a new Bini.js project targeting Web and install its dependencies:

>_Terminal
$ npx create-bini-app@latest my-app --platform web
$ cd my-app
$ npm install

Or use the interactive prompt and select Web Application:

>_Terminal
$ npx create-bini-app@latest
>_Terminal
? Select target platform:
> Web Application
Windows Desktop
Linux Desktop
macOS Desktop
Android
iOS
 
↑↓ navigate • ⏎ select

Step 3: Develop

Run dev to start the Vite dev server with HMR:

>_Terminal
$ npm run dev
Tip: Web is the default platform - --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 --install if 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:

>_Terminal
$ npx create-bini-app@latest my-app --platform web
$ cd my-app
$ npm install

Or use the interactive prompt and select Web Application:

>_Terminal
$ npx create-bini-app@latest
>_Terminal
? Select target platform:
> Web Application
Windows Desktop
Linux Desktop
macOS Desktop
Android
iOS
 
↑↓ navigate • ⏎ select

Step 3: Develop

Run dev to start the Vite dev server with HMR:

>_Terminal
$ npm run dev
Tip: Web is the default platform - --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 git on Debian and Ubuntu.

Step 2: Create the project

Create the Project

Create a new Bini.js project targeting Web and install its dependencies:

>_Terminal
$ npx create-bini-app@latest my-app --platform web
$ cd my-app
$ npm install

Or use the interactive prompt and select Web Application:

>_Terminal
$ npx create-bini-app@latest
>_Terminal
? Select target platform:
> Web Application
Windows Desktop
Linux Desktop
macOS Desktop
Android
iOS
 
↑↓ navigate • ⏎ select

Step 3: Develop

Run dev to start the Vite dev server with HMR:

>_Terminal
$ npm run dev
Tip: Web is the default platform - --platform web is optional.

Development Server

Start the development server with HMR (Hot Module Replacement). This command is the same on every OS:

>_Terminal
$ 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 .env files
  • Error overlay with bini-overlay

Production Server

Build and serve your application in production mode:

>_Terminal
$ 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 renderToPipeableStream is 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
// 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

>_Terminal
$ 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/:

dist
index.html
about
index.html
blog
[slug]
index.html
docs
[...slug]
index.html
js
index-[hash].js
css
index-[hash].css
/
/about
/blog/:slug
/docs/*
  • Highlighted files are fully pre-rendered pages for / and /about
  • /blog/:slug and /docs/* are shell pages
  • js/ and css/ hold your compiled JavaScript and CSS files

Hydration and Shell Pages

For dynamic routes, bini-ssg injects a marker script:

dist/blog/[slug]/index.html
<!-- injected by bini-ssg -->
<script>window.__BINI_SHELL__=true;</script>

Your client entry checks this flag:

src/main.tsx
// 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

>_Terminal
$ npm run deploy

Generated Files

The target you choose determines what bini-deploy creates:

PlatformRuntimeFile Generated
Node.jsNode.js- (bini-server reads src/app/api/ directly)
NetlifyEdge Functions (Deno)netlify/edge-functions/api.ts + netlify.toml
VercelEdge Runtimeapi/index.ts + vercel.json
CloudflareWorkersworker.ts + wrangler.toml
DenoDenoserver/index.ts

Deployment Options

  • SPA + API Server: Build with npm run build, deploy with npm start (requires Node.js)
  • Pre-rendered Static: Build with npm run build, deploy the dist/ folder to any static hosting
  • Edge/Serverless: Use npm run deploy to generate platform-specific entry files

Node.js Deployment

For Node.js hosts (Railway, Render, Fly.io, a VPS):

>_Terminal
$ 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/.

vite.config.ts
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()],
})
Was this helpful?