Bun Native Frontend

Zero-config React/TS/CSS with HMR, served on the same port as your API. The only frontend mode that supports compiling the whole app (server + frontend assets) into a single executable.

Overview

With frontend: { type: 'bun' }, tekir uses Bun's built-in HTMLBundle pipeline. Each .html file in resources/ becomes a route; <script> and <link> tags are bundled by Bun automatically. No config file, no plugin install. JSX, TypeScript, CSS, and import with { type: 'file' } embeds all work out of the box.

Setup

index.ts
const { router, service, start } = await tekir({
  config: { app: { port: 3000 } },
  frontend: { type: 'bun' }   // HTML lives in resources/
})

router.get('/api/hello', () => ({ message: 'from server' }))

start()

Place HTML pages in resources/. The filename becomes the route: resources/index.html → /, resources/about.html → /about, and so on.

<!-- resources/index.html -->
<!DOCTYPE html>
<html>
  <head>
    <title>tekir + Bun</title>
    <link rel="stylesheet" href="./styles.css" />
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="./main.tsx"></script>
  </body>
</html>
// resources/main.tsx
import { createRoot } from 'react-dom/client'
import App from './App'

createRoot(document.getElementById('root')!).render(<App />)

Multi-page apps

Drop additional HTML files alongside index.html. tekir registers each one as a static route automatically:

resources/
├── index.html       → /
├── about.html       → /about
├── dashboard.html   → /dashboard
├── main.tsx
└── styles.css

HMR & Dev Server

In development (NODE_ENV !== 'production'), Bun watches resources/and pushes hot module updates over the same port your API listens on. There is no second dev process and no proxy hop — one server, one port, everything reloads on save.

Production Build

For production, bundle the frontend ahead of time. Bun emits minified, hashed assets and inlines them into the response:

tekir build
tekir serve

Single Executable

Bun's --compileembeds your HTML, bundled JS/CSS, and the Bun runtime into one binary. Drop it on a server and run it — no Bun, Node, or node_modulesneeded at runtime.

# Build a single executable that bundles
# server + Bun runtime + frontend assets
tekir build --compile

# Run anywhere — no Bun, Node, or node_modules needed
./server

Cross-compile and advanced flags (--target, --define, --bytecode, --sourcemap etc.) live in the fluent API reference.

Frontend Environment Variables

Bun's HTMLBundle pipeline does not substitute process.env at bundle time, so secrets in .env stay on the server by default. To expose values to the browser, write a regular API route and fetch it:

.env
# .env (server only — never reaches the browser)
DATABASE_URL=postgres://localhost/mydb
APP_KEY=...

PUBLIC_API_URL=https://api.example.com
PUBLIC_APP_NAME=My App
index.ts
// index.ts — expose values you want the browser to read.
router.get('/api/config', () => ({
  apiUrl: process.env.PUBLIC_API_URL,
  appName: process.env.PUBLIC_APP_NAME,
}))
resources/main.tsx
// resources/main.tsx
const config = await fetch('/api/config').then((r) => r.json())
console.log(config.apiUrl, config.appName)

Limits

Bun native is the fastest and simplest path, but it's an SPA-style bundler — no server-side rendering, no file-based dynamic routes, no pre-rendering. If you need any of those, use Next.js instead. If you want the Vite plugin ecosystem (Vue, Svelte, Solid, custom transforms), switch to Vite.