Web apps in TypeScript, served by Rust.
Write routes, loaders, actions and pages the way other frameworks do. FSR compiles them at build time and the Rust server runs the result, with no JavaScript engine on the server.
No JavaScript, Just Fast IR
Real sources and the IR the compiler produced from them, regenerated on every build of this site. The last tab is a loader the compiler refuses and the error it reports.
import type { Ctx } from "@snapfire/fsr";
export async function load({ session }: Ctx) {
const count = Object.values(session.cart).reduce((n, q) => n + q, 0n);
return { count };
}(let count (reduce (values (session cart)) 0 (fn (n q) (+ n q))))
FSR against React on the server
The same pages with the same props, rendered by the FSR interpreter, by React in V8 and by React in QuickJS.
How FSR compares
Pick a question. FSR's answer comes first with what backs it up, then how five other frameworks answer it.
Independent teams each deploy their own site into one app, served by one Rust binary.
All four render on the server.
Server-rendered by default. The build moves only what needs the browser into islands.
rendered routes/page.tsx lowered static
src/ui/home/ReactCounter.tsx lowered
src/ui/RequestTrace.tsx client
islands src/ui/home/ServerCounter.tsx server 2 handlersPrerendered pages regenerate while the host serves. Service data serves stale while it refreshes. Server islands and SPA navigation.
1.4x React on V8 for the same page. 12.5 µs a render.
Each team builds and deploys its own app. The host mounts it at a path inside the shared layout, session and store.
[sites.docs] artifact = "sites/docs" [sites.learn] artifact = "sites/learn"
Copy it and a directory to any server. No Node.
shop the binary config/ app.toml app/generated/ plan.sexp serve/static/ static files
React, Vue, Web Components or Tera
An island is the interactive part of a page. Write it in React, in Vue or as a web component. A server island runs on the server instead, so the browser downloads no code for it. Pages are TSX or Tera templates. Every kind of island shares the page and the store.
import { useStore } from "@snapfire/fsr-client/react";
import { demoCount } from "@src/store";
export function ReactCounter() {
const [n, setN] = useStore(demoCount, 0);
return (
<div className="forge-counter">
<button onClick={() => setN(n - 1)}>−</button>
<output>{n}</output>
<button onClick={() => setN(n + 1)}>+</button>
</div>
);
}
// in the page
<Island when="visible"><ReactCounter /></Island>// A server island written in React
import { useStore } from "@snapfire/fsr-client/react";
import { demoCount } from "@src/store";
export function ServerCounter() {
const [n, setN] = useStore(demoCount, 0);
return (
<div className="forge-counter">
<button onClick={() => setN(n - 1)}>−</button>
<output>{n}</output>
<button onClick={() => setN(n + 1)}>+</button>
</div>
);
}
// in the page
<Island when="visible" mode="server"><ServerCounter /></Island><script setup lang="ts">
import { useStore } from "@snapfire/fsr-client/vue";
import { demoCount } from "@src/store";
const n = useStore(demoCount, 0);
</script>
<template>
<div class="forge-counter">
<button @click="n.value--">−</button>
<output>{{ n.value }}</output>
<button @click="n.value++">+</button>
</div>
</template>import { get, set, subscribe }
from "@snapfire/fsr-client/store";
import { demoCount } from "@src/store";
const count = () => get(demoCount) ?? 0;
export class ForgeCounter extends HTMLElement {
connectedCallback() {
const out = this.querySelector("output")!;
const [down, up] = this.querySelectorAll("button");
down.onclick = () => set(demoCount, count() - 1);
up.onclick = () => set(demoCount, count() + 1);
out.textContent = String(count());
subscribe(demoCount, (n) => (out.textContent = n));
}
}
customElements.define("forge-counter", ForgeCounter);
// in the page
<Island when="visible"
define="@src/elements/forge-counter.ts">
<forge-counter>
<div className="forge-counter">
<button aria-label="Decrease">−</button>
<output>0</output>
<button aria-label="Increase">+</button>
</div>
</forge-counter>
</Island><!-- routes/demos/counter/page.tera -->
<div class="forge-counter" id="tera-counter">
<button hx-get="/demos/counter?__fragment"
hx-vals='{"by": -1}' hx-target="#tera-counter"
hx-swap="outerHTML">−</button>
<output>{{ n }}</output>
<button hx-get="/demos/counter?__fragment"
hx-vals='{"by": 1}' hx-target="#tera-counter"
hx-swap="outerHTML">+</button>
</div>
// routes/demos/counter/page.loader.ts
export async function load({ query }: Ctx) {
return { n: Number(query.n ?? "0")
+ Number(query.by ?? "0") };
}
export const store = ({ data }) =>
({ "home/count": data.n });Four commands
The CLI makes the project, serves it with reload, checks it and writes the tree you copy to a server.
- Make a project
fsr new shop --with reactwrites the config, routes, a layout and an import map. - Run it
fsr devrebuilds and reloads the page when a file changes. - Check it
fsr doctorruns thirteen checks for failures development hides. - Bundle it
fsr bundlewritesdist/. Copy it and the binary to the server.
Built into the host
The stock host handles sessions, identity, service calls, caching and deployment checks, so the application code doesn't implement them.
Mounted sites
Mount one application inside another at a prefix. It shares the layout, session and store, in one document with one copy of React.
Deploy by copying
A binary and a directory. No Node and no npm ci on the server.
dist/ shop the binary config/ app/ plan.sexp, read at boot serve/ static files
Server-held tokens
The host stores bearer tokens and attaches them to service calls. TypeScript never sees one.
Typed service calls
Clients generated from OpenAPI and Protobuf, called as services.shopping.placeOrder().
Pages in Tera
Write a page as a Tera template when it needs no TypeScript.
Caching
Cache a whole page or just the slow part of it. Service data serves stale while it refreshes.
Streaming and prerendering
Send the shell first and stream a slow section. Render a blog at build time, serve it as files and regenerate it while the server runs.
This site runs on FSR
The docs and the tutorials are separate FSR applications mounted inside this one. Each has its own routes and content. They share this site's layout, session and store.

The main app, React pages rendered by the host

Docs, its own app at /fsr/docs

Tutorials, its own app at /fsr/learn
Other projects
Build your first page
The first tutorial starts from an empty directory and ends with a page served by Rust.