Skip to content
liustackPublic

Latest commit

 

History

2,135 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Tifa

The full-stack Go framework for Gophers.

简体中文 · Writing an app · Build and run · Dev server · Example app

Follow @liustack on X Go reference Go version CGO off License Not backed by Y Combinator Users unknown

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.

Why it exists

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.

What it looks like

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

Highlights

  • 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 Load and every Layout function 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 Load returns.
  • Mistakes caught at build time. Duplicate paths, a Layout reading a parameter it does not own, a Load result 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.

One small server is enough

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.

Start an app

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:9527

The 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.

Try the example

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:9527

Use it with a coding agent

skills/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/tifa

A new app also gets an AGENTS.md from tifa new. The skill covers the same ground with more detail and links to the docs.

Documentation

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

Talk to us

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":

liustack WeChat official account

Contributing

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.

Star History

Star History Chart

License

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.