The full-stack 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 | nginx | The Go HTTP server in the binary |
| 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 HTML cache, 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+/route.tsx
import { route, type Data } from './+route'
export default function OrderPage() {
const order = route.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?: Data }) => ({
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- 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.
You need Go 1.27, Node 20.19 or 22.12 and later (what Vite 8 asks for) and pnpm (npm works too). 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 can take a minute or two. 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 order workbench: two shops, an order list, order details, a shipping form, sales stats with charts, and three kinds of 404. 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:9527| Doc | Read it when |
|---|---|
| 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 caching |
| 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 |
| Configuration | Deploying: the port, body limit, VMs, render timeout and page cache |
| 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. Come find me on X: @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.
MIT. A program built with tifa embeds Unicode CLDR locale data, whose notices go with the program when you distribute it, see docs/build.md.