Patterns · for app developers

The parts bin

I want a page at a URL

Make a directory under app/routes/ matching the path and put a page.tsx in it. That is the whole of it. routes/pricing/ serves /pricing; routes/product/[id]/ serves /product/{id} with params.id.

I want that page to have data

Put a page.loader.ts beside it exporting load. Its return type becomes the page's props type, generated into @generated/client under the route's name.

I want a title and a description

Export meta from the same loader.

ts
export const meta = ({ data }: { data: { name: string } }) => ({
  title: `${data.name} · Storefront`,
  description: `Buy ${data.name}.`,
  head: [og("type", "product"), canonical(`/product/${data.slug}`)],
});

I want a header on every page

routes/layout.tsx with a routes/layout.loader.ts beside it. Keep the loader free of route parameters and the layout survives every navigation beneath it, DOM and island state intact.

I want part of the page to stream

Add loading.tsx beside the page. Its presence is the declaration; nothing in the page or the loader changes.

I want a button that does something

actions.ts beside the page, exporting a declared action. Call it from an island through the generated actions handle. It re-runs the affected loaders on success.

I want to guard a route

Middleware, or a guard at the top of the loader or action. A guard that reads nothing external runs before any socket opens.

I want state the server keeps

A server island: <Island when="visible" mode="server">. The browser ships no component code and every interaction is a round trip.

I want state two islands share

The store. Seed it from a loader's store export, read it with a typed key. Prefix your keys: the store is one flat map.

I want a third-party package

text
fsr add app <name>@<version>

It vendors an ESM bundle under app/vendor/, commits it and adds the import-map entry. Nothing is fetched at deploy time.

I want a font, a favicon or a meta tag on every page

The root layout's meta. The helpers in @snapfire/fsr/head cover the common rows and an object literal with a tag covers the rest. An inner route overrides one row by naming the same element again.

ts
export const meta = () => ({
  head: [
    linkTag("preconnect", "https://fonts.example"),
    linkTag("stylesheet", "https://fonts.example/inter/inter.css"),
    { tag: "link", rel: "icon", type: "image/x-icon", href: "/static/favicon.ico" },
  ],
});

I want a script on every page

A module app/src/main.ts imports for its effect. There is no head row for a script; a library comes in through fsr add and the entry imports it. A vendor that must load as a script element is a module that creates that element when the page needs it.

Two modules: one runs the banner and calls the other from its consent callback; the other builds the dataLayer, pushes arguments the way Google's snippet does and appends the gtag.js script. The measurement id is a [public] value the root layout's store seeds the browser with. Tutorial 095 builds it end to end.

I want a value that differs per deployment

[public] in config/app.toml, overridden in a deployment's overlay, read in a loader as ctx.config.<key>. The declaration types the read, so a misspelt key is a build error. The value reaches the browser through whatever the loader returns, so a secret is not one of them.

A secret goes in the same files as a c5store .c5encval: the decryptor, the key name and the ciphertext, which c5cli writes into TOML, YAML or JSON. The host decrypts it while loading, with the key of that name from config/private_keys or from C5_SECRETKEY_<NAME>. Loader::secrets is where a Rust host changes that.

I want to call a backend

Declare a client in config/app.toml and put its contract in app/clients/. It arrives in every loader and action as services.<name>, typed from the contract. A transport = "mock" and a .mock.json beside it is how you develop and test against it.

I want to call my own Rust

Mark an impl block and register it. The loader stays where it is; only the computation moves.

rust
#[native]
impl Digest {
  pub fn words(&self, bodies: Vec<String>) -> i64 {
    bodies.iter().map(|b| b.split_whitespace().count() as i64).sum()
  }
}
rust
Host::from(...).native("digest", Arc::new(Digest))
ts
const words = native.digest.words({ bodies });

Rust is synchronous and so is this, so words returns a number rather than a promise. Only what the block declares pub crosses, so a module holding another calls it as an ordinary method and that one never appears in TypeScript. There is no contract, no transport and no interceptor chain, because nothing is crossing a boundary: the declaration comes from the signature, which fsr reads with syn before anything compiles, so the two cannot drift.

Reach for a service instead when the thing genuinely is one, something over a wire that a document already describes, or when you want the cache and the interceptors that come with a call that crosses.

A service with no document behind it is declared the same way, with #[service] in place of #[native]. The attribute writes both the transport and the contract off the signatures, so the call crosses the boundary properly: arguments and answers are checked, interceptors run, a #[cache] policy is honoured and a Result<T, ServiceError> arrives as the failure it names. A method taking a Caller is told who is calling without the body passing anything. caller.require()? refuses an anonymous call. The build writes the contract to generated/contracts/rust.json; the host refuses a disagreement at boot, so a build that fell behind the Rust fails on startup rather than on a call.

I want to test without a browser

fsr test <app>. Body tests replay a loader against mocked services and assert on what it returned. Page tests render over a DOM. Route tests go through the host. None of them need Node.

I want search engines to see one URL per page

Name the address the deployment is reached at, and the host writes every rel=canonical and rel=alternate absolute:

toml
[document]
origin = "https://example.com"

A crawler reads those two as absolute URLs only, so a path on either is an ignored tag rather than a weaker one. It is the scheme and the host with nothing after it; a trailing slash or a path is refused at boot. One preferred origin for the deployment, not the host a request arrived on, because two names serving the same pages is what a canonical link exists to collapse.

I want to know which host a request came in on

ctx.host in a loader or an action, once [server] hosts names the hosts the deployment answers on. The list is an allowlist rather than a format: a Host it does not hold answers null instead of the value the client sent, and with the key unset the header is never read. Whatever sits in front of the host has to set the header for the value to mean anything, which is proxy_set_header Host $host; under a matching server_name in nginx.

I want to know what the host actually did

Read the boot report. Every route with its pattern, every loader with whether it lowered, every component with whether the server can render it, every static root, everything ignored. When something does not work, the report has usually already said so.

I want to see what my TypeScript became

The IR inspector shows real sources beside the exact IR the lowerer produced, including one that is deliberately refused.

Built with SnapFire FSR. Pure Rust runtime, zero Node.js on the server.

Proudly Created by Excerion Sun LLC