Build high-performance server-rendered applications using Dutchy Design System with Bun's native JSX support. No build step required.
These are NOT traditional React components. Bun's SSR renders JSX to static HTML on the server - there is no React runtime on the client. No useState, no useEffect, no client-side hydration. For interactivity, use vanilla JavaScript.
mkdir my-dutchy-app
cd my-dutchy-app
bun init -y
# Install dependencies
bun add react react-dom
bun add -d @types/react @types/react-dom
// tsconfig.json
{
"compilerOptions": {
"jsx": "react-jsx",
"moduleResolution": "bundler",
"paths": { "@/*": ["src/*"] }
}
}
// src/index.ts
import { serve } from 'bun';
import { renderToReadableStream } from 'react-dom/server';
import { createElement } from 'react';
serve({
port: 3000,
async fetch(req) {
const { default: HomePage } = await import('./routes/index.tsx');
const stream = await renderToReadableStream(
createElement(HomePage, { request: req })
);
return new Response(stream, {
headers: { 'Content-Type': 'text/html' }
});
}
});
console.log('Server running at http://localhost:3000');
Native routes option in Bun.serve(). Simplest approach for quick prototypes.
serve({
routes: {
'/': HomePage,
'/users/:id': handler,
'/api/*': notFound,
}
});
req.params.id
Bun's FileSystemRouter class with Next.js-style conventions.
pages/
├── index.tsx → /
├── about.tsx → /about
└── blog/
└── [slug].tsx → /blog/:slug
match.params
Method-per-file convention with full control. Auto-detects React components.
routes/
├── index.tsx → GET /
├── api/users/
│ ├── index.ts → GET
│ ├── post.ts → POST
│ └── $id/
│ └── put.ts → PUT
_param_id
// src/routes/index.tsx
import type { FC } from 'react';
import Layout from '@/ui/Layout';
interface HomePageProps {
request: Request;
}
const HomePage: FC<HomePageProps> = ({ request }) => {
return (
<Layout
title="Home"
scripts={['/assets/js/app.js']}
>
<section class="py-20 border-b-8 border-primary">
<div class="container mx-auto px-4">
<h1 class="font-display text-6xl font-bold uppercase">
Hello<span class="text-primary">.</span>
</h1>
<button
id="cta-btn"
class="bg-primary text-white px-8 py-4 mt-8"
>
Click Me
</button>
</div>
</section>
</Layout>
);
};
export default HomePage;
// public/assets/js/app.js
// Button click handler
document.getElementById('cta-btn')
.addEventListener('click', () => {
alert('Button clicked!');
});
// Mobile menu toggle
const menuBtn = document.getElementById('menu-btn');
const mobileNav = document.getElementById('mobile-nav');
menuBtn?.addEventListener('click', () => {
mobileNav.classList.toggle('hidden');
});
// Form validation
const form = document.getElementById('contact-form');
form?.addEventListener('submit', (e) => {
const email = form.querySelector('[name="email"]');
if (!email.value.includes('@')) {
e.preventDefault();
alert('Invalid email');
}
});
// Data attributes for dynamic content
document.querySelectorAll('[data-product-id]')
.forEach(card => {
card.addEventListener('click', () => {
const id = card.dataset.productId;
console.log('Product:', id);
});
});
my-dutchy-app/
├── src/
│ ├── index.ts # Server entry
│ ├── routes/
│ │ ├── index.tsx # GET /
│ │ ├── about/
│ │ │ └── index.tsx # GET /about
│ │ ├── contact/
│ │ │ ├── index.tsx # GET /contact
│ │ │ └── post.ts # POST /contact
│ │ └── api/
│ │ └── users/
│ │ ├── index.ts # GET /api/users
│ │ ├── post.ts # POST /api/users
│ │ └── $id/
│ │ ├── index.ts # GET /api/users/:id
│ │ ├── put.ts # PUT /api/users/:id
│ │ └── delete.ts # DELETE /api/users/:id
│ ├── ui/
│ │ ├── Layout/
│ │ ├── Header/
│ │ ├── Footer/
│ │ ├── Button/
│ │ └── Card/
│ └── utils/
│ └── loadRoutes.ts
├── public/
│ └── assets/
│ ├── css/
│ └── js/
├── package.json
└── tsconfig.json
File-based routing. Filename determines HTTP method: index.tsx = GET, post.ts = POST
Reusable JSX components. One folder per component with index.tsx as entry point.
Utilities like loadRoutes.ts for automatic route discovery.
Static assets served directly. CSS, JS, images, fonts.
Dynamic route segments. $id becomes :id in the URL pattern.
Complete documentation with examples for routing, components, forms, API routes, and more.