D.
Dutchy
Bun 1.3+

Using with
Bun.

Build high-performance server-rendered applications using Dutchy Design System with Bun's native JSX support. No build step required.

SSR-Only Components

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.

Quick Start

1

Create Project

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
2

Configure TypeScript

// tsconfig.json
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "moduleResolution": "bundler",
    "paths": { "@/*": ["src/*"] }
  }
}
3

Create Server

// 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');

Three Routing Approaches

Bun 1.2.3+

Built-in Routes

Native routes option in Bun.serve(). Simplest approach for quick prototypes.

serve({
  routes: {
    '/': HomePage,
    '/users/:id': handler,
    '/api/*': notFound,
  }
});
Params: req.params.id
Any Bun

FileSystem Router

Bun's FileSystemRouter class with Next.js-style conventions.

pages/
├── index.tsx      → /
├── about.tsx      → /about
└── blog/
    └── [slug].tsx → /blog/:slug
Params: match.params
Recommended

Custom loadRoutes

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
Params: _param_id

Component Example

1 JSX Component (Server)

// 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;

2 Vanilla JS (Client)

// 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);
    });
  });

Best For

  • Content-focused websites (blogs, docs, marketing)
  • Server-rendered forms with POST handlers
  • SEO-critical applications
  • Fast initial page loads
  • Server-driven interactivity

Consider CSR Instead For

  • Complex stateful UI (dashboards, editors)
  • Real-time updates (chat, live data)
  • Heavy client-side state management
  • Single-page application behavior
  • Optimistic UI updates

Project Structure

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

routes/

File-based routing. Filename determines HTTP method: index.tsx = GET, post.ts = POST

ui/

Reusable JSX components. One folder per component with index.tsx as entry point.

utils/

Utilities like loadRoutes.ts for automatic route discovery.

public/

Static assets served directly. CSS, JS, images, fonts.

$param/

Dynamic route segments. $id becomes :id in the URL pattern.

Read the Full Docs

Complete documentation with examples for routing, components, forms, API routes, and more.