Getting started
Introduction
What Tern is, and how its pieces fit.
#Why Tern?
SvelteKit has one of the best developer experiences on the web, but its server code runs on Node. Tern keeps the parts that make SvelteKit pleasant, such as file-based routes, load functions, form actions and layouts, and runs everything server-side in Go.
- Real Svelte 5. Not a lookalike. Your components, the Svelte ecosystem and your editor tooling all keep working.
- Go on the server.
loadfunctions and form actions are plain Go, built onnet/http. - End-to-end types. The structs your Go
Loadreturns become TypeScript types for your Svelte pages, generated automatically. - Server-side rendering.
tern buildcompiles the server renderer of your Svelte components to Go, so pages render in Go and hydrate in the browser. No Node in production. - One binary.
tern buildembeds the client and the renderer into a single static Go executable. - Progressive enhancement. Forms work without JavaScript, and get better with it.
#Getting started
You need Go 1.26+ and Node.js 22+.
npm create tern@latest
It asks where to put the project and whether to start bare or with a full app. A bare app is the minimal, blog or api template, optionally styled with Tern UI. The full app (saas) has accounts with passkeys and two-step sign-in, teams with invitations and roles, a dashboard, settings and per-team data in SQLite. Then it offers to create a git repository and install packages. Without npm, go run github.com/Unfield/Tern/cmd/tern@latest new does the same. Then:
cd my-app
npm run dev
Open http://localhost:5173. npm run build produces build/my-app, a single binary that serves the whole app.
New to Tern? The tutorial builds a complete app, a reading list with accounts, tests and a deploy, one step per page.
#A taste
src/routes/
├── +layout.svelte
├── +page.svelte
└── blog/slug.param/ # → /blog/[slug]
├── page.server.go
└── +page.svelte
// src/routes/blog/slug.param/page.server.go
package page
import "github.com/Unfield/Tern/kit"
type Data struct {
Title string `json:"title"`
Body string `json:"body"`
}
func Load(ctx *kit.Ctx) (Data, error) {
post, err := posts.Find(ctx.Param("slug"))
if err != nil {
return Data{}, kit.NotFound()
}
return Data{Title: post.Title, Body: post.Body}, nil
}
<!-- src/routes/blog/slug.param/+page.svelte -->
<script lang="ts">
import type { PageProps } from './$types';
let { data }: PageProps = $props(); // fully typed from the Go struct
</script>
<h1>{data.title}</h1>
<article>{data.body}</article>
Dynamic directories use Go-safe names, because Go import paths cannot contain brackets: slug.param for [slug], lang.optional for [[lang]], path.rest for [...path], and app.group for (app). See examples/todos for a complete app with sessions, form actions, a protected route group and an API endpoint, examples/dashboard for a shadcn-svelte dashboard, examples/status for a self-hosted status page with live updates, background jobs and notifications, and examples/accounts for user accounts with passkeys.
#Status
| Milestone | Scope | State |
|---|---|---|
| v0.1 | Router, loads, actions, endpoints, typegen, dev server, single-binary build | Done |
| v0.2 | Server-side rendering and hydration | Done |
| v0.3 | Prerendering, streaming, deployment guides | Done |
| v0.4 | Typed links, navigation hooks, selective reloading, CSP, a shadcn-svelte dashboard example, docs site | Done |
| v1.0 | Plugins, base paths, public environment variables, the stability promise | Done |
| v1.1 | Translations, responsive images, service workers, sessions, metrics, rate limiting and CORS plugins | Done |
| v1.2 | Markdown and content, realtime, jobs and migrations, OAuth/OIDC, email, storage, typed endpoints with OpenAPI, starter templates, experimental Go components | Done |
| v1.3 | Live components (Go component logic on the server, typed markup with tern check, several processes through a broker), the MCP server, Tern UI |
Done |
| v1.4 | Form validation from Go struct tags, checked in the browser too; the complete Tern UI with charts, data tables and auth screens; tern ui add; npm create tern; an analytics example app |
Done |
| v1.5 | Native rendering: tern build compiles the server renderer to Go, 12× faster than QuickJS on a 100-row table and faster than SvelteKit on a Node cluster |
Done |
| v1.6 | Fully native rendering; user accounts with passkeys, email verification, password resets and signed-in devices; Tern's own texts translatable | Done |
| v1.7 | Two-step sign-in, sign-in with GitHub, Google or OIDC into accounts, email changes and rate limits; a faster JSON encoder and smaller binaries; render benchmarks in CI | Done |
| v1.8 | A full app to start from (tern new asks bare or full); native rendering phase 2: page data as Go values and Svelte's renderer and helpers in Go, 272 µs for the 100-row table |
Done |
| v1.9 | Structured logging with sinks and a log view in tern dev; HTTPS from the app itself; security signals with TLS and HTTP fingerprints; tern deploy to servers, Fly.io and registries with zero-downtime restarts; tern.land |
Done |
| v1.10 | Tracing with OTLP export and a waterfall in tern dev; error reports grouped by cause, from server and browser, to Sentry; testing loads, actions and whole apps from Go, with tests in every template |
Done |
| v1.11 | Teams in accounts with invitations, roles and an audit log; billing with Stripe: plans, trials, seats and limits; team and pricing screens in Tern UI; the saas template with both | Done |
| v1.12 | Pages rendered on demand with stale-while-revalidate; tags and revalidation across processes; the cache package in memory, on disk or in Redis; cache headers per route; pages without JavaScript | Done |
| v1.13 | Binaries without the JavaScript engine when every page compiles to Go; emails rendered natively | Done |
| v1.14 | Apps for assistants over MCP, with OAuth consent and tokens bound to the app; client metadata documents and resource indicators in the OIDC provider; a tutorial that builds a complete app | Done |
| v1.15 | Admin screens from Go structs; dev tools in tern dev with an error overlay; streaming endpoints; full-text search with a search box; feature flags; rooms and shared documents in live components; tern lsp with VS Code and Zed extensions; tern add and a plugins page; fewer allocations in native renders |
Done |
| v1.16 | Secure by default: security headers, a Content-Security-Policy, rate limits and routes closed until they say who may use them; typed SQL from .sql files; migration and vulnerability checks; settings and secrets, encrypted fields, safehttp, uploads without photo metadata |
Done |
#Documentation
- Guide: everything you need to build an app
- User accounts: sign-up, sign-in with passwords and passkeys, email verification, password resets, devices
- Live components: component logic in Go, on the server
- MCP server:
tern mcpfor AI coding tools - Tern UI: accessible components with Tern's own design
- Coming from SvelteKit: what maps to what
- Architecture: how Tern works internally
- Deployment: running Tern in production
- Benchmarks: how fast the pieces are
- Stability: what the version numbers promise
- Roadmap: what is planned, and in what order
- Decision records: why things are the way they are
#Contributing
Contributions are welcome. Please read CONTRIBUTING.md and our Code of Conduct first. To report a security issue, follow SECURITY.md.
#License
MIT © The Tern Authors
Tern is an independent project and is not affiliated with or endorsed by the Svelte team.