Edge-native. Zero ops.
A full-stack starter that runs at the speed of light.
One Cloudflare Worker: Hono +D1 + Inertia v3 with in-process SSR. React, Svelte, or Vue — your choice. Auth, migrations, tests — wired end to end. wrangler deploy and you're live on 300+ edge locations.
Working code, not wiring homework
Auth, migrations, SSR, tests — running end to end on Workers, not left for you to wire.
Auth, complete
Register, login, logout, forgot/reset password and Google OAuth — PBKDF2 (Web Crypto), DB-backed sessions, CSRF.
Inertia v3 + SSR
Full HTML on first load, SPA after. In-process SSR inside the Worker, 409 version negotiation, partial reloads, flash.
Edge-native D1
SQLite at the edge via D1. Versioned migrations via Wrangler. Raw prepared statements — no ORM, zero abstraction tax.
One Worker, three clients
React 19, Svelte 5, and Vue 3 templates with Tailwind CSS v4 — same auth, SSR, and test suite.
Web Crypto passwords
PBKDF2-HMAC-SHA256 via crypto.subtle — zero-dependency, runs natively in Workers. No WASM, no native modules.
Production-grade ops
Per-request logging, security headers (CSP, nosniff, frame denial), /health check, HSTS. wrangler deploy = live.
Why Kilat exists
Because your first week should be business logic, not infrastructure.
Edge-native means zero ops
No Docker, no VPS, no process supervisor. The whole stack is Cloudflare Workers: wrangler dev for local, wrangler deploy for production. D1 is the database, Workers Static Assets serves the client bundle, Web Crypto handles password hashing.
Boring means predictable
Routes in one place, SQL in one file, env in one module — no guessing where things live. No clever tricks to figure out. Whoever comes next — human or AI — can read it and change it without fear of breaking it.
Built for AI agents
Conventions codified in AGENTS.md, validation errors with exact shapes, strict TypeScript that fails at compile time, deterministic tests as the safety net. A codebase an agent can extend without inventing conventions stays coherent.
For the full reasoning, read the philosophy page.
One Worker. Three clients.
Pick a framework and styling — the same Login page, in your stack.
import { Head, Link, useForm } from "@inertiajs/react";import AuthLayout from "../components/AuthLayout";import Field from "../components/Field";import "../components/AuthLayout.css";
export default function Login() { const form = useForm({ email: "", password: "" });
const submit = (e: React.FormEvent) => { e.preventDefault(); form.post("/login"); };
return ( <AuthLayout> <Head title="Login" /> <h1 className="auth-sub">Welcome back</h1> <form onSubmit={submit} noValidate> <Field id="email" label="Email" error={form.errors.email}> <input id="email" type="email" autoComplete="email" value={form.data.email} onChange={(e) => form.setData("email", e.target.value)} /> </Field> <button className="btn btn-primary btn-block" type="submit" disabled={form.processing}> {form.processing ? "Signing in…" : "Sign in"} </button> </form> </AuthLayout> );}<script lang="ts"> import { Link, useForm } from '@inertiajs/svelte' import AuthLayout from '../components/AuthLayout.svelte' import Field from '../components/Field.svelte'
let { googleEnabled = false, notice = null } = $props()
const form = useForm({ email: '', password: '' })
function submit(e: SubmitEvent) { e.preventDefault() form.post('/login') }</script>
<AuthLayout> <h1 class="auth-sub">Welcome back</h1> <form onsubmit={submit} novalidate> <Field id="email" label="Email" error={form.errors.email}> <input id="email" type="email" bind:value={form.email} onchange={() => form.clearErrors('email')} /> </Field> <button class="btn btn-primary btn-block" type="submit" disabled={form.processing}> {form.processing ? 'Signing in…' : 'Sign in'} </button> </form></AuthLayout><script setup lang="ts">import { Head, Link, useForm } from "@inertiajs/vue3";import AuthLayout from "../components/AuthLayout.vue";import Field from "../components/Field.vue";
defineProps<{ googleEnabled?: boolean; notice?: string | null }>();
const form = useForm({ email: "", password: "" });
function submit() { form.post("/login");}</script>
<template> <Head><title>Login</title></Head> <AuthLayout> <h1 class="auth-sub">Welcome back</h1> <form @submit.prevent="submit" novalidate> <Field id="email" label="Email" :error="form.errors.email"> <input id="email" type="email" v-model="form.email" @change="form.clearErrors('email')" /> </Field> <button class="btn btn-primary btn-block" type="submit" :disabled="form.processing"> {{ form.processing ? "Signing in…" : "Sign in" }} </button> </form> </AuthLayout></template>FAQ
The short version. For the long version, read the comparison article.
How is Kilat different from Next.js, Nuxt, or SvelteKit?
Those are meta-frameworks that couple a server model to one client framework and leave auth and persistence to you. Kilat is a single Cloudflare Worker with Hono + D1 + Inertia v3, auth already wired, raw SQL instead of an ORM, and React, Svelte, or Vue as a swappable template. Pick a meta-framework for RSC or edge middleware; pick Kilat for a wired, ownable, edge-native stack.
How is Kilat different from Dulak?
Dulak runs on Bun (Bun.serve + bun:sqlite + Bun.build). Kilat runs on Cloudflare Workers (Wrangler + D1 + esbuild). Same philosophy, same structure, same Inertia v3 SSR pattern — different runtime. Dulak is for self-hosting on a VPS; Kilat is for zero-ops edge deployment. The dual esbuild build pattern (pre-build SSR to dist/ssr.js) is Kilat's key innovation — Wrangler's internal esbuild can't compile .svelte or .vue files, so we pre-build SSR to plain JS.
Why Cloudflare Workers and not Bun?
Zero ops. No Docker, no VPS, no process supervisor. wrangler deploy and you're live on 300+ edge locations. D1 replicates read-only copies to the nearest edge automatically. The free tier is enough for most apps. Pick Bun (Dulak) for maximum control and synchronous SQLite; pick Workers (Kilat) for zero-ops edge deployment.
Do I have to use React?
No. Kilat ships templates for React 19, Svelte 5, and Vue 3 — each with vanilla CSS or Tailwind CSS v4. The server side (Hono, D1, auth) is identical across all of them.
Why no ORM?
ORMs were built to help humans avoid writing SQL. In 2026, AI generates correct, optimized raw SQL on demand — the human-friction problem ORMs solved is gone. What remains is the cost: an ORM is a dependency you upgrade, audit, and debug, and it adds a layer between you and the database that hides the actual query plan. D1 with prepared statements is explicit and zero-dependency. You see the exact SQL that runs.
Why PBKDF2 and not argon2?
Workers doesn't have native argon2. PBKDF2 via Web Crypto (crypto.subtle) is zero-dependency and runs natively in the Workers runtime. Workers caps PBKDF2 at 100K iterations (OWASP recommends 600K, which throws NotSupportedError) — 100K is still OWASP-acceptable for PBKDF2-HMAC-SHA256.
Is it production-ready?
Yes — every guardrail a deployed app needs is wired and tested, not scaffolded: PBKDF2 passwords (Web Crypto), DB-backed sessions (not JWT), CSRF origin checks, security headers (CSP, nosniff, frame denial), versioned migrations, HSTS, and an E2E suite that boots the full app. The server side is not a demo — it is the same code path that runs in production. What is missing is your business logic, and that is the point.
Will this still work in 5–10 years?
Yes, and here is why: Kilat is a boilerplate you fork and own, not a framework that can be deprecated out from under you. The stack is chosen for longevity — Cloudflare Workers, Hono, D1, and Inertia are stable, pinned versions with no pending rewrites. When a dependency does release a new major, it is a deliberate migration you schedule and test — not an emergency. And because there is no ORM, no SDK, no proprietary abstraction layer, the code you own is just TypeScript, SQL, and HTTP — readable and maintainable by any developer or AI agent, now and in 2036.