Getting started
What you need
Rust, and nothing else. The build installs the two binaries it uses.
cargo install snapfire_compiler
cargo install snapfire_fsr_clisnapfirec is the compiler that bundles TypeScript. fsr is the command you drive everything else with. Check them:
fsr --helpScaffold an application
fsr new storefrontThat writes a project directory: a config/app.toml, an app/ holding routes/, src/, styles/ and an importmap.json. There is no package.json and no node_modules, and there will not be one.
storefront/
config/app.toml
app/
importmap.json
routes/
layout.tsx
page.tsx
page.loader.ts
not-found.tsx
error.tsx
src/main.ts
styles/app.css
types/@snapfire/What it does not write is a framework. The import map names the client, its standard library and its store. The pages are written against @snapfire/fsr-authoring/template, which types the placements without React. A bare application is the default because most pages do not need one.
Ask for React with --with, which vendors it and adds the four import-map entries:
fsr new storefront --with reactfsr use <app> react does the same to a project that already exists. Both take vue, elements, htmx and tera as well. The scaffold prints the line for you:
next fsr use storefront/app react # only if the application wants React; also vue, elements, htmx or teraRun it
fsr dev storefront/appThe host boots, prints a report of everything it found and serves on the address in config/app.toml. The report is worth reading every time: it lists each route with its pattern, each loader with whether it lowered, each component with whether the server can render it and every static root. When something does not work, the report usually already said so.
Edit app/routes/page.tsx and the browser reloads.
The commands you will actually use
| Command | What it does |
|---|---|
fsr dev <app> | Build, serve and watch. The development loop. |
fsr build <app> | Build once, write generated/ and dist/. |
fsr check <app> | Typecheck without emitting. |
fsr serve <app> | Serve what is already built, no watcher. |
fsr test <app> | Run the application's tests, without a browser. |
fsr use <app> <direction> | Give the app react, vue, elements, htmx or tera. |
fsr add <app> <pkg@version> | Vendor a dependency and add its import-map entry. |
fsr types <app> | Fetch the type declarations the import map implies. |
fsr bundle <app> --out <dir> | Write the deploy tree. |
fsr prerender <app> | Render every eligible route to a file, paths included. |
fsr doctor <app> | Check a project or a deploy tree for what will bite in production. |
Where things live
config/app.toml holds the configuration: what to listen on, what the document's title is, how sessions are keyed, which service clients exist and where the static roots are. Most of it has a default. The report tells you what the host inferred.
It is the first rung of a ladder, read in this order:
| File | When |
|---|---|
app.toml | always |
<release_env> | RELEASE_ENV |
<app_env> | APP_ENV |
<region> | APP_REGION |
<app_env>-<region> | both |
bundle.toml | in a deploy tree, written by fsr bundle |
Each as .toml then .yaml. Absent is normal: a checkout with only app.toml runs. A deployment adds its own overlay without touching the base. The boot report lists the files it actually read, in order.
app/importmap.json is the allowlist of bare specifiers. The compiler is given it as --import-map, and an import with no entry fails the build rather than shipping a broken module. fsr add is what writes entries.
app/generated/ is written by every build and read by nothing you edit. It holds the plan the host serves, the route types your loaders constrain themselves against and the props types your pages import.
The lab
Change page.loader.ts to return a second field, then read it in page.tsx. Now delete the field from the loader but leave the page reading it: the build fails, on the line, before the browser ever sees the page. That is the contract in What SnapFire FSR is doing its job.