Update documentation to correctly refer to this as the "Bun Rendering API" rather than "Bake framework". This better reflects that it's a rendering system/API rather than a full framework. Changes: - Rename title from "Bake: Full-Stack Web Framework" to "Bun Rendering API" - Replace "Bake" references with "Bun Rendering API" - Replace "framework" with "rendering system/API" in descriptions - Update variable names from "customFramework" to "customConfig" - Clarify this is an experimental rendering API 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
11 KiB
Bun Rendering API
The Bun Rendering API is an experimental server-side rendering system that provides React Server Components support, static site generation, and hot module reloading. It's currently under heavy development and available in canary builds.
⚠️ Warning: The Rendering API is experimental and APIs may change significantly.
Overview
The Bun Rendering API provides:
- React Server Components with automatic client/server separation
- File-based routing with Next.js-style conventions
- Hot Module Reloading (HMR) for development
- Static Site Generation for production builds
- Configuration-driven development with React integration
- TypeScript support out-of-the-box
Quick Start
Create a bun.app.ts configuration file:
// bun.app.ts
/// <reference path="node_modules/bun/src/bake/bake.d.ts" />
export default {
port: 3000,
app: {
framework: "react", // Built-in React integration
},
};
Start with:
bun run bun.app.ts
CLI Commands
Development Server
# Run configuration file
bun run bun.app.ts
bun bun.app.ts # shorthand
Production Build
# Build static site
bun build --app bun.app.ts
# Build with specific entry point
bun build --app ./src/app.tsx
Configuration
Built-in React Integration
// bun.app.ts
export default {
app: {
framework: "react", // Uses built-in React integration
},
};
Requires React 19 experimental:
bun add react@experimental react-dom@experimental react-server-dom-webpack@experimental
Custom Configuration
import type { Bake } from "bun";
const customConfig: Bake.Framework = {
// File-based routing configuration
fileSystemRouterTypes: [{
root: "pages",
style: "nextjs-pages", // or "nextjs-app-ui", "nextjs-app-routes"
serverEntryPoint: "./server.tsx",
clientEntryPoint: "./client.tsx",
layouts: true,
ignoreUnderscores: true,
extensions: ["tsx", "jsx"],
}],
// Static file serving
staticRouters: ["public"],
// React Server Components
serverComponents: {
separateSSRGraph: true,
serverRuntimeImportSource: "react-server-dom-webpack/server",
serverRegisterClientReferenceExport: "registerClientReference",
},
// Build options
bundlerOptions: {
client: {
conditions: ["browser"],
},
server: {
conditions: ["node"],
},
ssr: {
conditions: ["react-server"],
},
},
// Fast Refresh for React
reactFastRefresh: {
importSource: "react-refresh/runtime",
},
};
export default {
app: {
framework: customConfig,
},
};
File-Based Routing
Basic Routes (Next.js Pages Style)
pages/
├── index.tsx # /
├── about.tsx # /about
├── blog/
│ ├── index.tsx # /blog
│ └── [slug].tsx # /blog/:slug
└── _layout.tsx # Layout for all pages
// pages/index.tsx
export default function HomePage() {
return <h1>Welcome!</h1>;
}
// pages/blog/[slug].tsx
interface Props {
params: { slug: string };
}
export default function BlogPost({ params }: Props) {
return <h1>Post: {params.slug}</h1>;
}
App Router Style
app/
├── page.tsx # /
├── layout.tsx # Root layout
├── about/
│ └── page.tsx # /about
└── blog/
├── page.tsx # /blog
├── layout.tsx # Blog layout
└── [slug]/
└── page.tsx # /blog/:slug
Layouts
// pages/_layout.tsx (or app/layout.tsx)
interface Props {
children: React.ReactNode;
params?: Record<string, string>;
}
export default function RootLayout({ children }: Props) {
return (
<html>
<head>
<title>My App</title>
</head>
<body>
<nav>Navigation</nav>
<main>{children}</main>
</body>
</html>
);
}
React Server Components
Server Components (Default)
Server components run on the server and can access databases, APIs, etc:
// pages/posts.tsx - Server Component
async function getPosts() {
const response = await fetch("https://api.example.com/posts");
return response.json();
}
export default async function PostsPage() {
const posts = await getPosts();
return (
<div>
<h1>Posts</h1>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.content}</p>
</article>
))}
</div>
);
}
Client Components
Add "use client" for browser-only features:
// components/Counter.tsx - Client Component
"use client";
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
Mixed Usage
// pages/dashboard.tsx - Server Component
import Counter from "../components/Counter"; // Client Component
async function getUser() {
// Server-side data fetching
return { name: "John", id: 1 };
}
export default async function Dashboard() {
const user = await getUser();
return (
<div>
<h1>Welcome, {user.name}!</h1>
{/* This renders on the server */}
<p>User ID: {user.id}</p>
{/* This adds client-side interactivity */}
<Counter />
</div>
);
}
Server Entry Points
Custom Server Implementation
// server.tsx
import type { Bake } from "bun";
export async function render(
request: Request,
meta: Bake.RouteMetadata
): Promise<Response> {
const { pageModule, layouts, params, styles, modules } = meta;
// Build component tree with layouts
let route = <pageModule.default params={params} />;
for (const layout of layouts) {
const Layout = layout.default;
route = <Layout params={params}>{route}</Layout>;
}
// Full HTML document
const page = (
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Bun + React Server Components</title>
{styles.map(url => (
<link key={url} rel="stylesheet" href={url} data-bake-ssr />
))}
</head>
<body>
{route}
{modules.map(url => (
<script key={url} type="module" src={url} />
))}
</body>
</html>
);
// This uses React Server Components to render
// See /workspace/bun/src/bake/bun-framework-react/server.tsx for implementation
return renderToResponse(page, request);
}
// For static site generation
export async function prerender(meta: Bake.RouteMetadata) {
// Generate static HTML and RSC payload
return {
files: {
"/index.html": htmlBlob,
"/index.rsc": rscPayload, // For client navigation
},
};
}
// For dynamic routes
export async function getParams(meta: Bake.ParamsMetadata) {
return {
pages: [{ slug: "hello" }, { slug: "world" }],
exhaustive: true, // All pages generated at build time
};
}
Client Entry Point
// client.tsx
import { hydrateRoot } from "react-dom/client";
import { onServerSideReload } from "bun:bake/client";
// Hydrate the server-rendered content
// Implementation handles RSC payload and client navigation
// See /workspace/bun/src/bake/bun-framework-react/client.tsx
// Hot reload support in development
if (import.meta.env.DEV) {
onServerSideReload(async () => {
// Reload page content without full page refresh
await reloadCurrentPage();
});
}
Static Site Generation
Build Command
bun build --app bun.app.ts
This generates:
- Static HTML files for each route
- Optimized JavaScript bundles
- CSS files with automatic optimization
- RSC payload files for client navigation
Dynamic Routes
// pages/blog/[slug].tsx
export default function BlogPost({ params }: { params: { slug: string } }) {
return <h1>Post: {params.slug}</h1>;
}
// Generate static paths
export async function getStaticPaths() {
const posts = await fetchBlogPosts();
return {
paths: posts.map(post => ({ params: { slug: post.slug } })),
exhaustive: true, // Build all pages at build time
};
}
Development Features
Hot Module Reloading
The Rendering API provides fast development feedback:
- Component updates preserve React state
- CSS hot reloading without page refresh
- Server-side changes trigger automatic reload
- Error overlay with stack traces
Development Modules
// Available in development
import { onServerSideReload } from "bun:bake/client";
// Debug utilities (DEV only)
if (import.meta.env.DEV) {
console.log(window.$bake.currentCssList);
window.$bake.goto("/new-page");
}
HMR WebSocket
Development server uses WebSocket at /_bun/hmr for:
- File change notifications
- Error reporting
- CSS reload signals
- React Fast Refresh
Bundler Integration
CSS Support
// Automatic CSS loading
import "./styles.css";
// CSS is automatically:
// - Bundled and optimized
// - Hot reloaded in development
// - Managed during client navigation
Asset Handling
// Assets served at /_bun/asset/<key>
// Automatic optimization and caching
import logo from "./logo.png";
function Header() {
return <img src={logo} alt="Logo" />;
}
API Reference
Rendering Options
interface Options {
framework: Framework | "react";
bundlerOptions?: BundlerOptions;
plugins?: BunPlugin[];
}
RouteMetadata
interface RouteMetadata {
readonly pageModule: any; // Route module
readonly layouts: ReadonlyArray<any>; // Layout modules
readonly params: Record<string, string> | null; // Route parameters
readonly modules: ReadonlyArray<string>; // JS files to load
readonly modulepreload: ReadonlyArray<string>; // Files to preload
readonly styles: ReadonlyArray<string>; // CSS files
}
Special Modules
"bun:bake/server"- Server manifest for client components"bun:bake/client"- Client-side reload hooks"bun:bake/dev"- Development utilities
Production Deployment
Build Output
bun build --app bun.app.ts
Generates dist/ with:
index.html- Static HTMLindex.rsc- RSC payload for navigationassets/- Optimized JS/CSS bundles
Server Deployment
// For dynamic server rendering
const server = Bun.serve({
port: 3000,
app: {
framework: "react",
bundlerOptions: {
define: { "process.env.NODE_ENV": '"production"' },
},
},
});
Current Limitations
Since the Rendering API is experimental:
- ⚠️ APIs may change significantly
- ⚠️ Limited documentation and examples
- ⚠️ Requires canary Bun builds
- ⚠️ React 19 experimental required
- ⚠️ No official plugin ecosystem yet
Examples
Server Components App
// bun.app.ts
export default {
app: {
framework: "react",
},
};
// pages/index.tsx - Server Component
async function getData() {
return { message: "Hello from server!" };
}
export default async function HomePage() {
const data = await getData();
return <h1>{data.message}</h1>;
}
This documentation reflects the actual implementation of the Rendering API as found in the Bun codebase. The API is experimental and under active development.