The full-stack Go framework for Gophers.
简体中文 · Writing an app · Build and run · Dev server · Example app
Write the backend in Go and the pages in React. Tifa compiles the whole site into one executable that you copy to a small VPS and run. No Node in production, no process manager, no reverse proxy, no Redis.
It is for Go developers whose business logic and data already live in Go, who want to write pages with the React ecosystem, and who do not want to keep a Node service running in production just for server-side rendering.
Tifa is very early. APIs will change, and many compiler optimizations are not done yet.
I wanted to ship a small product on TanStack Start. My other VPSes were full, Black Friday was still weeks away and I did not feel like buying another one, so I dug out an old box with 1 CPU and 1 GB of RAM. I had Codex set up the usual stack in one go: nginx, Node.js, PM2, TanStack Start, Redis, BullMQ, cron and SQLite.
It did not really run. It crawled.
So I started hacking on Tifa. Once it is done, deploying should mean copying one Go binary to the VPS, and that is it.
| On a 1 CPU / 1 GB VPS | The usual stack | With Tifa |
|---|---|---|
| Reverse proxy and certificates | nginx + certbot | The Go HTTP server in the binary, which gets its own Let's Encrypt certificate and speaks HTTP/2 |
| Runtime and process manager | Node.js + PM2 | One Go executable |
| React SSR | TanStack Start | TanStack Router, rendered by a pure Go QuickJS inside the Go process |
| Cache | Redis | An in-process render memo for pages, a CDN in front if you want one, plus whatever you keep in Go memory |
| Queues and scheduled jobs | BullMQ + cron | Goroutines you start in OnStart |
| Database | SQLite | SQLite, with a pure Go driver so CGO stays off |
I still do not know what to call it. A React SSR framework with no Node runtime? A React SSR framework with a Go backend? Both sound a little unhinged. Under the hood it is Go, a pure Go build of QuickJS, and TanStack Router.
One folder per page. Go loads the data, React shows it:
// app/projects/project+/orders/order+/route.go → /projects/{project}/orders/{order}
func Load(c *tifa.Ctx) (orders.Order, error) {
return orders.GetOrder(c.Param("project"), c.Param("order"))
}// app/projects/project+/orders/order+/page.tsx
import { page, type PageData } from './route.gen'
export default function OrderPage() {
const order = page.useLoaderData() // typed from the Go return value
return <h2>{order.id}</h2>
}
// title and meta tags are written into the HTML on the server
export const head = ({ loaderData }: { loaderData?: PageData }) => ({
meta: [{ title: `Order ${loaderData?.id}` }],
})go get -tool github.com/liustack/tifa/cmd/tifa
go tool tifa dev # hot-reloads components, restarts on Go changes
go tool tifa build # bin/<app>, with the frontend embedded
go tool tifa check # analysis, vet, tests and the TypeScript check in one go- One file to deploy. Pure Go,
CGO_ENABLED=0, cross-compiles like any Go program. The browser bundle, styles and server renderer are embedded in the executable. - Real HTML on the first request. Go runs the page's
Loadand everyLayoutfunction first, then renders React to complete HTML inside the process with a pure Go QuickJS. Search engines and link previews see the content, title and meta tags. Each page renders whole in one pass: there is no streaming and there are no React Server Components. - Your frontend tools keep working. Vite, Tailwind and shadcn work as usual on your machine. The server runs one Go file and never installs Node.
- Types from Go to React. The TypeScript type of each page's data is generated from what its Go
Loadreturns. - Mistakes caught at build time. Duplicate paths, a Layout reading a parameter it does not own, a
Loadresult that cannot become TypeScript: the build stops and names the file and line. - Fast after the first page. TanStack Router takes over in the browser. Each navigation is one request for data, and Layouts that are already loaded are not fetched again.
The render memo is on for every page, signed-in ones like a cart or a bill included. Every request fetches fresh data first and reuses the last render only when nothing changed, so a page is never stale. A render is reused only for the same data and the same session, so one person's page never reaches another. The app writes no caching code.
With the render memo off, so every request renders:
| Requests/s | P50 | P99 | Peak RSS | |
|---|---|---|---|---|
| Tifa | 42 | 19.2 ms | 44.8 ms | 57 MiB |
| Next.js | 80 | 10.6 ms | 28.3 ms | 435 MiB |
| TanStack Start | 115 | 6.7 ms | 23.1 ms | 347 MiB |
| React Router | 75 | 11.1 ms | 29.1 ms | 433 MiB |
Next.js, --jitless |
26 | 35.6 ms | 64.6 ms | 337 MiB |
TanStack Start, --jitless |
30 | 30.2 ms | 60.6 ms | 276 MiB |
React Router, --jitless |
24 | 37.4 ms | 70.5 ms | 355 MiB |
Tifa has no JIT. The --jitless rows run Node without its JIT too, for reference: they answer 0.6 to 0.7 times the requests of Tifa.
Every request asks for gzip. TanStack Start does not compress pages by default, so it sends about 146 KB a page, against about 16 KB for Tifa and Next.js and 44 KB for React Router. Compressing costs Next.js 16% of its requests a second, and Start would likely pay about as much if it compressed.
Each server on one core of a Google Cloud c2d-standard-8 VM (AMD EPYC 7B13), default settings, the six workbench pages of the example shop. The full comparison has the method and every number.
Whole visits, played back from real browsers on the example shop: shoppers and staff on one server at once, Tifa's render memo empty at the start (2026-11-09):
| Page views/s | Full load P50 / P99 | |
|---|---|---|
| Tifa | 79.8 | 2.8 / 110 ms |
| Next.js | 34.6 | 42.2 / 91 ms |
| TanStack Start, uncompressed | 87.6 | 29.3 / 53 ms |
| React Router | 54.8 | 51.8 / 85 ms |
Page views a second with 8 visitors at once and no pause between pages. The latencies are at 6 visits a second, half of what Next.js sustains. Whole visits has the mix and what the numbers mean.
You need Go 1.27, Node 20.19 or 22.12 and later (what Vite 8 asks for) and pnpm (npm works too). Pages need React 19.3 or later, which tifa new installs. Node only builds the front end: the program you deploy never needs it. tifa dev does not run on Windows yet, use WSL.
go run github.com/liustack/tifa/cmd/tifa@latest new myapp
cd myapp
go mod tidy
pnpm install
go tool tifa dev # http://127.0.0.1:9527The first tifa dev compiles QuickJS and SQLite once, which takes about 7 seconds on an Apple M4 (an empty build cache, the whole app). Later builds use Go's build cache.
tifa new writes a home page with a Go Load, a POST the page calls from the browser, and a test of both. The module path is the folder name unless you pass --module example.com/myapp.
examples/shop is a small shop in two halves: an order workbench for the owner (order list, order details, a shipping form, sales stats with charts) and a storefront for customers in Chinese and English (product search and filters, product pages, a cart, a checkout form and a journal in Markdown). You need Go 1.27, Node 20.19 or 22.12 and later, and pnpm:
cd examples/shop
pnpm install --frozen-lockfile
go tool tifa build
./bin/shop # http://127.0.0.1:9527skills/tifa is an agent skill for Claude Code. It tells the agent how a Tifa app is laid out, which mistakes the build and the server render punish, and how to test and deploy. Copy the folder into your project's .claude/skills/ (or into ~/.claude/skills/ for all projects):
git clone --depth 1 https://github.com/liustack/tifa.git tifa-src
mkdir -p .claude/skills && cp -r tifa-src/skills/tifa .claude/skills/tifaA new app also gets an AGENTS.md from tifa new. The skill covers the same ground with more detail and links to the docs.
| Doc | Read it when |
|---|---|
| Tutorial | You are new to Tifa and want to build a small app from the first folder to a running program |
| Guides: forms, uploads, sign-in, database, background work, email, languages, streaming | Adding a common feature and you want a tested way to do it |
| Philosophy | You want to know why a Tifa app is organized the way it is |
| Structuring an app | Deciding where domain code, storage and shared resources go, and which way packages may import each other |
| Writing an app | Laying out folders, routes, Layouts, hooks, 404 pages, head tags and Cache for CDNs |
| Sessions | Signing users in and out, keeping sessions in Redis or SQL, or showing who is signed in |
| Content | Keeping a blog or docs as Markdown files and reading them in Go |
| Build and run | You want to know what tifa build generates and how the program answers requests |
| Dev server | Running tifa dev locally, or a change did not reload |
| Deploying | Putting the program on a server or in a container, or behind a CDN or proxy |
| Configuration | Deploying: the port, HTTPS, body limit, VMs, render timeout, the render memo and the Cache-Control of pages with Cache |
| Architecture | You want to know how a request, tifa build, tifa dev, the VM pool and the render memo work inside |
| Render memo | You want to know why pages, signed-in ones included, are reused safely with nothing to set up |
| Performance | You want to know whether a small VPS can carry your traffic |
| Research studies | You want the full method and data behind a number, or to see whether an idea was already tried |
| Testing | Contributing to Tifa itself |
Issues are welcome any time: open one. Tell us what you built, where it hurt, and what should come next. Follow the liustack WeChat official account, and come find me on X: @liustack. What changes in Tifa and what comes next are shared on WeChat and X.
The liustack WeChat official account: AI startup opportunities, indie-dev insights, and hands-on AI tooling, delivered as they happen. Scan the QR code in WeChat, or search for "liustack":
Tifa does not accept pull requests. A framework like this touches the compiler, the bundler, server rendering and developer experience at once, and a change in one pulls on the others. Getting that right takes a lot of context, so every line is written and reviewed by one author. Two ways to help:
- Open an issue. Bugs, ideas, confusing errors, unclear docs. A clear issue with steps to reproduce helps more than a patch, and issues shape what gets built next.
- Fork it. Under MIT your copy is yours to change and publish. CONTRIBUTING.md has the notes for working on a fork.
MIT. A program built with Tifa embeds locale data and time zones from Unicode CLDR, ICU and go-intl, whose notices go with the program when you distribute it, see docs/build.md.
The gopher in the banner is a redrawn Go gopher, originally designed by Renee French and used under CC BY 4.0.

