Skip to content

Navigation Menu

Sign in
Sign up

Repository files navigation

KilatJS

A Bun-native, HTML-first framework that renders real pages, treats UI frameworks as renderers, and makes SEO the default—not a feature.

Bun Runtime SSR Only SEO Guaranteed


🎯 What is KilatJS?

KilatJS is a server-first web framework inspired by the simplicity of PHP and the power of modern component libraries. It rejects the complexity of SPAs, hydration, and client-side routing in favor of real HTML rendered on the server.

One-Line Positioning

"Request → Load → Render HTML → Response. That's it."


⚡ Quick Start

Create a New Project

The easiest way to start is using the KilatJS CLI:

# Create a new project in my-app folder
bunx kilat create my-app
cd my-app
bun install
bun run dev

Visit http://localhost:3000 🎉


📖 CLI Commands

Command Description
kilat create [dir] Create a new project from template
kilat dev Start development server with HMR + Live Reload
kilat build Build for production
kilat serve Run production server (bun dist/server.js)
kilat preview Preview build output

Development Output

⚡ KilatJS Dev Server
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 ➜ http://localhost:3000
 ➜ HMR + Live Reload enabled
 ➜ Edit files and see changes instantly
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

📁 Project Structure

KilatJS uses a simplified, root-level structure:

my-app/
├── public/ # Static assets & index.html
├── routes/ # SSR routes (index.tsx, about.tsx)
├── components/ # React/Kilat components
├── kilat.config.ts # Configuration
├── input.css # Tailwind input
├── index.client.tsx # (Optional) React client entry
├── App.tsx # (Optional) Client-side app
└── dist/ # Production build

⚛️ React Client Support (Opt-in)

While KilatJS is HTML-first, you can easily add a full React client-side application for complex interactive features.

  1. Auto-detection: If index.client.tsx exists in your root, KilatJS will automatically bundle it.
  2. Setup: Create a public/index.html with a mount point:
<div id="root"></div>
<script type="module" src="/client/index.client.js"></script>
  1. Route: Define clientRoute: "/client" in kilat.config.ts to host your SPA.

📄 Route Contract

Each route file can export:

// SEO meta tags
export const meta = {
 title: "Page Title",
 description: "Page description",
 robots: "index,follow",
 ogTitle: "Open Graph Title",
 ogDescription: "OG description",
 ogImage: "https://example.com/image.jpg",
};
// Server-side data loading
export async function load(ctx) {
 const data = await fetchData();
 return { items: data };
}
// HTTP method handlers (POST, PUT, DELETE, etc.)
export async function POST(ctx) {
 const formData = await ctx.request.formData();
 // Process form...
 return Response.redirect("/success", 302);
}
// Page component (receives data from load())
export default function Page({ data, params, state }) {
 return (
 <div>
 {data.items.map((item) => (
 <p>{item.name}</p>
 ))}
 </div>
 );
}

🔧 Tutorial: Building a Blog

Step 1: Create Layout Component

src/components/Layout.tsx:

interface LayoutProps {
 children: React.ReactNode;
 title?: string;
}
export function Layout({ children, title = "My Blog" }: LayoutProps) {
 return (
 <div className="min-h-screen bg-gray-50">
 <header className="bg-white shadow">
 <nav className="max-w-4xl mx-auto px-4 py-4">
 <a href="/" className="text-xl font-bold">
 My Blog
 </a>
 <div className="float-right space-x-4">
 <a href="/" className="text-gray-600 hover:text-gray-900">
 Home
 </a>
 <a href="/blog" className="text-gray-600 hover:text-gray-900">
 Blog
 </a>
 <a href="/about" className="text-gray-600 hover:text-gray-900">
 About
 </a>
 </div>
 </nav>
 </header>
 <main className="max-w-4xl mx-auto px-4 py-8">{children}</main>
 </div>
 );
}

Step 2: Create Blog List Page

src/routes/blog/index.tsx:

import { Layout } from "../../components/Layout";
export const meta = {
 title: "Blog - My Site",
 description: "Read our latest articles",
};
export async function load() {
 // In real app, fetch from database
 const posts = [
 {
 id: 1,
 slug: "hello-world",
 title: "Hello World",
 excerpt: "My first post...",
 },
 {
 id: 2,
 slug: "getting-started",
 title: "Getting Started",
 excerpt: "Learn how to...",
 },
 ];
 return { posts };
}
export default function BlogPage({ data }) {
 return (
 <Layout>
 <h1 className="text-3xl font-bold mb-8">Blog</h1>
 <div className="space-y-6">
 {data.posts.map((post) => (
 <article key={post.id} className="bg-white p-6 rounded-lg shadow">
 <h2 className="text-xl font-semibold">
 <a href={`/blog/${post.slug}`} className="hover:text-blue-600">
 {post.title}
 </a>
 </h2>
 <p className="text-gray-600 mt-2">{post.excerpt}</p>
 </article>
 ))}
 </div>
 </Layout>
 );
}

Step 3: Create Dynamic Blog Post Page

src/routes/blog/[slug].tsx:

import { Layout } from "../../components/Layout";
export const meta = {
 title: "Blog Post",
 description: "Read this article",
};
export async function load({ params }) {
 // params.slug contains the URL parameter
 const post = await getPostBySlug(params.slug);
 if (!post) {
 throw new Response("Not Found", { status: 404 });
 }
 return { post };
}
async function getPostBySlug(slug: string) {
 // In real app, fetch from database
 const posts = {
 "hello-world": { title: "Hello World", content: "This is my first post!" },
 "getting-started": {
 title: "Getting Started",
 content: "Let me show you how...",
 },
 };
 return posts[slug] || null;
}
export default function BlogPostPage({ data, params }) {
 return (
 <Layout>
 <article>
 <h1 className="text-4xl font-bold mb-4">{data.post.title}</h1>
 <div className="prose max-w-none">{data.post.content}</div>
 </article>
 <a href="/blog" className="text-blue-600 mt-8 inline-block">
 ← Back to Blog
 </a>
 </Layout>
 );
}

Step 4: Create API Route

src/routes/api/posts.ts:

import { RouteContext } from "kilatjs";
const posts = [
 { id: 1, title: "Hello World", slug: "hello-world" },
 { id: 2, title: "Getting Started", slug: "getting-started" },
];
// GET /api/posts
export async function GET(ctx: RouteContext) {
 const search = ctx.query.get("search")?.toLowerCase();
 let results = posts;
 if (search) {
 results = posts.filter((p) => p.title.toLowerCase().includes(search));
 }
 return new Response(JSON.stringify({ data: results }), {
 headers: { "Content-Type": "application/json" },
 });
}
// POST /api/posts
export async function POST(ctx: RouteContext) {
 const body = await ctx.request.json();
 const newPost = {
 id: posts.length + 1,
 title: body.title,
 slug: body.title.toLowerCase().replace(/\s+/g, "-"),
 };
 posts.push(newPost);
 return new Response(JSON.stringify({ data: newPost }), {
 status: 201,
 headers: { "Content-Type": "application/json" },
 });
}
/**
 * NEW: Standalone API Mode with Swagger Documentation
 * KilatJS provides a dedicated core for building pure APIs.
 */
// example/server.ts
import KilatJS, { swagger, RouteContext } from "kilatjs/core";
const app = new KilatJS();
// Register Swagger documentation
app.use(
 swagger(app, {
 path: "/docs",
 title: "KilatJS API",
 description: "Automatic API documentation",
 })
);
// Route with metadata for Swagger
app.post(
 "/api/echo",
 {
 summary: "Echo request",
 body: { text: "string" }, // Optional: for documentation
 response: { echo: {}, receivedAt: "string" }, // Optional: for documentation
 },
 async (ctx: RouteContext) => {
 const body = await ctx.request.json();
 return { echo: body, receivedAt: new Date() };
 }
);
app.listen(4000);

Step 5: Create Contact Form with PRG Pattern

src/routes/contact.tsx:

import { Layout } from "../components/Layout";
export const meta = {
 title: "Contact Us",
 description: "Get in touch with us",
};
// Handle form submission
export async function POST({ request }) {
 const formData = await request.formData();
 const name = formData.get("name");
 const email = formData.get("email");
 const message = formData.get("message");
 // Save to database, send email, etc.
 console.log("Contact form:", { name, email, message });
 // PRG: Post-Redirect-Get pattern
 return Response.redirect("/contact/success", 302);
}
export default function ContactPage() {
 return (
 <Layout>
 <h1 className="text-3xl font-bold mb-8">Contact Us</h1>
 <form method="POST" className="max-w-md space-y-4">
 <div>
 <label className="block text-sm font-medium mb-1">Name</label>
 <input
 type="text"
 name="name"
 required
 className="w-full px-3 py-2 border rounded-lg"
 />
 </div>
 <div>
 <label className="block text-sm font-medium mb-1">Email</label>
 <input
 type="email"
 name="email"
 required
 className="w-full px-3 py-2 border rounded-lg"
 />
 </div>
 <div>
 <label className="block text-sm font-medium mb-1">Message</label>
 <textarea
 name="message"
 rows={4}
 required
 className="w-full px-3 py-2 border rounded-lg"
 />
 </div>
 <button
 type="submit"
 className="bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-700"
 >
 Send Message
 </button>
 </form>
 </Layout>
 );
}

src/routes/contact/success.tsx:

import { Layout } from "../../components/Layout";
export const meta = {
 title: "Message Sent",
};
export default function ContactSuccessPage() {
 return (
 <Layout>
 <div className="text-center py-12">
 <h1 className="text-3xl font-bold text-green-600 mb-4">
 ✓ Message Sent!
 </h1>
 <p className="text-gray-600 mb-8">
 Thank you for contacting us. We'll get back to you soon.
 </p>
 <a href="/" className="text-blue-600 hover:underline">
 ← Back to Home
 </a>
 </div>
 </Layout>
 );
}

🔐 Authentication Example

// src/routes/dashboard.tsx
export async function load({ request }) {
 const cookie = request.headers.get("cookie");
 const session = await getSession(cookie);
 if (!session) {
 // Redirect to login if not authenticated
 throw Response.redirect("/login", 302);
 }
 return { user: session.user };
}
export default function DashboardPage({ data }) {
 return (
 <div>
 <h1>Welcome, {data.user.name}!</h1>
 <form method="POST" action="/logout">
 <button type="submit">Logout</button>
 </form>
 </div>
 );
}

🛠️ Configuration Reference

// kilat.config.ts
import { defineConfig } from "kilatjs";
export default defineConfig({
 appDir: "./src", // Source directory (auto-detects routes/ or pages/)
 outDir: "./dist", // Production build output
 port: 3000, // Server port
 hostname: "localhost", // Server hostname
 publicDir: "./public", // Static assets directory
 tailwind: {
 enabled: true, // Enable Tailwind CSS
 inputPath: "./input.css",
 cssPath: "./styles.css",
 },
});

🔥 Why Bun Native?

KilatJS leverages Bun's runtime directly:

  • Bun.FileSystemRouter - Zero-config file-based routing
  • 🚀 Bun.serve() - HTTP server with WebSocket support
  • 📦 Bun.build() - Native TypeScript/JSX bundling
  • 💾 Bun.file() - Streaming file operations
  • 🔍 Bun.Glob - Pattern matching for routes

Performance:

  • 3x faster cold starts vs Node.js
  • Native TypeScript - no transpilation
  • Zero-copy streaming for static files

✅ Core Principles

  1. HTML is the protocol — Every response is complete, semantic HTML
  2. Server owns all truth — No client-side state management
  3. Every change = HTTP request — Forms, mutations, navigation
  4. UI frameworks render only — React for templating, not orchestration
  5. JavaScript is optional — Sites work without JS

⚡ Client-Side Interactivity

KilatJS is server-first, but you can add client-side interactivity using these patterns:

1. clientScript Export (Recommended)

Export a function that runs in the browser. TypeScript/LSP fully supports it!

// src/routes/counter.tsx
// Client-side script - auto-injected, LSP checks the code!
export function clientScript() {
 let count = 0;
 const countEl = document.getElementById("count")!;
 document.getElementById("decrement")!.onclick = () => {
 countEl.textContent = String(--count);
 };
 document.getElementById("increment")!.onclick = () => {
 countEl.textContent = String(++count);
 };
}
export default function CounterPage() {
 return (
 <div>
 <button id="decrement">-</button>
 <span id="count">0</span>
 <button id="increment">+</button>
 </div>
 );
}

The framework automatically injects the script - no manual <script> tags needed!

2. Alpine.js

Lightweight reactivity with declarative HTML attributes:

export default function AlpinePage() {
 return (
 <div x-data="{ count: 0 }">
 <button x-on:click="count--">-</button>
 <span x-text="count">0</span>
 <button x-on:click="count++">+</button>
 </div>
 );
}

3. HTMX

Server-driven UI updates without JavaScript:

<!-- Can use .html files directly in routes/ -->
<button hx-get="/api/greeting" hx-target="#result" hx-swap="innerHTML">
 Load
</button>
<div id="result"></div>

KilatJS supports .html files as routes - perfect for HTMX pages!

4. Native HTML Forms

Best for mutations - no JS required:

<form method="POST" action="/contact">
 <input name="email" type="email" />
 <button type="submit">Submit</button>
</form>

🛠️ Configuration Reference

// kilat.config.ts
import { defineConfig } from "kilatjs";
export default defineConfig({
 appDir: ".", // Root directory for routes/ and components/
 outDir: "./dist", // Production build output
 port: 3000, // Server port
 publicDir: "./public", // Static assets directory (auto-detects index.html)
 clientRoute: "/client", // (Optional) Path for React client SPA
 tailwind: {
 enabled: true, // Enable Tailwind CSS
 inputPath: "./input.css",
 cssPath: "./public/styles.css",
 },
});

⚡ Hybrid Approach

KilatJS uniquely allows you to mix HTML-First SSR with Full React CSR:

  • Use SSR for public pages, SEO, blogs, and landing pages.
  • Use CSR for complex dashboards, interactive editors, or apps requiring shared state.

Note: KilatJS keeps these worlds separate. SSR pages are pure HTML (no React runtime), while CSR pages are full React apps. This gives you the speed of a static site with the power of a modern app.


⚡ Standalone API & Documentation

KilatJS provides a dedicated core for building pure JSON APIs with automatic documentation.

Features

  1. Automatic Swagger Documentation: Use the swagger middleware to generate interactive documentation.
  2. Typed Routes: Use RouteOptions to provide metadata, request bodies, and response structures.
  3. Core API: Use the lightweight KilatJS core for JSON-first services.

Example

import KilatJS, { swagger, RouteContext } from "kilatjs/core";
const app = new KilatJS();
app.use(
 swagger(app, {
 path: "/docs",
 title: "My API Documentation",
 })
);
app.get(
 "/api/users",
 {
 summary: "List users",
 response: { users: [] },
 },
 (ctx) => {
 return { users: ["Alice", "Bob"] };
 }
);
app.listen(4000);

📝 License

MIT


Built with 💜 for the real web using Bun.

About

A Bun-native, HTML-first framework that renders real pages, treats UI frameworks as renderers, and makes SEO the default—not a feature.

Resources

Stars

14 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

AltStyle によって変換されたページ (->オリジナル) /