Skill: Runtime And Infra
Use this for TanStack Start routing, SSR, server functions, server routes,
Cloudflare Worker runtime, D1/R2, Durable Objects, and deployment-facing
changes. If the task touches current user, login, logout, or /api/user, also
read references/auth.md.
Stack
- React 19 + TanStack Start.
- File-based routes live under
app/src/routes/. - SSR entry goes through
app/src/server.ts, which exports the Worker handler. - Build emits
dist/server/server.jsanddist/client. - No Next.js, Remix, Astro,
app/src/pages, Hono app, Express app, or separate API framework. App-local API endpoints are TanStack server routes underapp/src/routes/api/**.
SSR Safety
- Every route renders on the server per request.
- Never touch
window,document,localStorage,navigator, ormatchMediaat module top level or during render. - Browser globals belong in
useEffect, event handlers, or guarded branches.
Server-Only Code
- Put server logic in
createServerFn(...).handler(...)or*.server.ts. - Secrets and Cloudflare bindings are read server-side per request.
- Do not pass secrets, bindings, or account tokens through React props.
- Use
createServerFn({ method: "POST" })for mutations such as generation submit, cost preview, media upload, workspace switch, database writes, and other operations that change user-owned state. Use GET only for pure reads.
Supercomputer Design Mode
fnf-webowns the parent inspector UI. The generated website owns only the child bridge that runs inside the iframe.- Child bridge code lives in
app/src/module/design-inspector:registry.ts,runtime.ts, andvite.ts. bun run buildis inspector-free by default: no inspector runtime, no source metadata, and no per-element debug attributes. SettingHF_DESIGN_INSPECTOR=1in the env turns the same build into the inspector-enabled one (for LOCAL work only).- The deploy platform CI sets
HF_DESIGN_INSPECTOR=1on every deploy build, so the live deployed site always carries the inspector. - There is ONE deploy per website (
higgsfield website deploy <website_id>), and it ships the live public site immediately. The live site is the surface Supercomputer Design mode opens. - Never hard-code
HF_DESIGN_INSPECTOR=1into thebuildscript and never hand-edit the build script to toggle it — the deploy build is controlled by CI, not by these scripts. - The design build attaches source metadata through callback refs and a
WeakMapregistry. It must not add per-element DOM attributes. - The design build instruments intrinsic DOM tags and ref-capable component usages, including small icon components and compound component members. This is automatic; agents must not add marker props by hand. Components that do not forward refs fall back to nearest DOM/heuristic metadata.
- Keep the guarded dynamic import in
app/src/routes/__root.tsx; do not make the inspector a static root import. Inspector-free tree-shaking depends on the compile-time__HF_DESIGN_INSPECTOR__guard. - Never manually write
data-hf-*attributes, source markers, inspector refs, or postMessage handlers in website components. The design-inspector module and Vite config own all child-side instrumentation. - The only selector state allowed in DOM is global state such as
body[data-selector-active="true"]while Design mode is active. - Do not log or post cookies, auth headers, tokens, local/session storage, input values, raw HTML, raw uploaded bytes, or raw result URLs from the inspector.
Server Routes
Use TanStack Start server routes for browser-safe API proxies such as
/api/user:
// app/src/routes/api/user.ts
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/api/user')({
server: {
handlers: {
GET: async () => {
return Response.json({ ok: true })
},
},
},
})
Server routes are part of the same Worker. Do not add Hono/Express or a second backend process.
app/src/routeTree.gen.ts is GENERATED — normally you don't hand-write it.
After adding a route (app/src/routes/api/user.ts,
app/src/routes/api/media/upload.tsx, …) the TanStack Router plugin
regenerates it on bun run dev/bun run build; a stale tree that imports a
nested route but never registers it as a child makes /api/user etc. look like
backend failures when the route was simply never registered.
But the deploy build typechecks against the COMMITTED tree. createFileRoute("/api/user")
type-checks its path string against routeTree.gen.ts, and the CI build runs
tsc and vite in parallel — so tsc sees whatever route tree you
committed, before Vite regenerates it. If you add a route, commit a stale tree,
and can't run the toolchain in the sandbox (no bun, or the npm registry is
blocked), CI fails with TS2345: '"/api/user"' is not assignable to keyof
FileRoutesByPath. Two ways out, in order:
- Regenerate locally and commit the result — run
bun run dev(orbuild) so the plugin rewritesrouteTree.gen.ts, then commit it. Preferred. - Hand-register the route when you genuinely can't run the toolchain. The
file is deterministic: add the
*RouteImport, the route const, all three route maps, theFileRoutesByPathmodule augmentation, and therootRouteChildrenentry — mirroring an existing route exactly. Then it typechecks and the plugin will just reproduce the same tree on the CI build.
Binary Upload Routes
Do not send files through JSON server-function input. Browser File, Blob,
ArrayBuffer, Uint8Array, base64 strings, and byte arrays must not be
serialized into createServerFn data. This can produce Maximum call stack size
exceeded, huge payloads, and corrupted upload sources.
For uploads, use an app-local multipart route:
// app/src/routes/api/media/upload.ts
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/api/media/upload')({
server: {
handlers: {
POST: async ({ request }) => {
const form = await request.formData()
const file = form.get('file')
if (!(file instanceof File)) {
return Response.json({ ok: false, code: 'missing_file' }, { status: 400 })
}
const bytes = new Uint8Array(await file.arrayBuffer())
return Response.json({
ok: true,
contentType: file.type,
size: bytes.byteLength,
})
},
},
},
})
Client code must use FormData and must not set the content-type header:
const form = new FormData()
form.append('file', file)
await fetch('/api/media/upload', { method: 'POST', body: form })
For generation flows, upload first and return a small media reference/id. The later generation JSON request should contain prompt/settings/media refs only, never raw bytes.
Route handlers must declare the HTTP methods the browser will use. For example,
a generation proxy route must expose POST, not only GET:
// app/src/routes/api/generate.ts
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/api/generate')({
server: {
handlers: {
POST: async ({ request }) => {
const body = await request.json()
return Response.json({ ok: true, body })
},
},
},
})
If a generated website shows Method Not Allowed, first check whether the browser is
POSTing to a route that only registered GET, or whether a mutation was built as
a GET server function. This commonly breaks generation submit/cost/media flows.
Cloudflare Bindings
- Read D1/R2 bindings through
app/src/lib/bindings.server.ts. - Use
import { env } from "cloudflare:workers"only in server-only modules. app/app.manifest.jsondeclares infra.app/wrangler.jsoncis build/dev input; the deploy platform overwrites authoritative bindings.
Live Data Warning
There is one deploy and one set of D1 and R2 resources backing it. Every
migration or data change hits live production data directly. env.HF_ENV is
always "production" on deployed builds; it does not give you a separate
database or bucket to test against.
- Prefer additive migrations.
- Avoid
DROP, destructiveUPDATE, and destructive backfills unless the user explicitly approves production data changes.
Durable Objects
If app/app.manifest.json declares "durableObject": "ClassName", also export the
class from app/src/server.ts:
export class ClassName extends DurableObject {
// ...
}
Containers/code sandboxes are not deployable through this template yet.
SEO Infrastructure
Every site with a public face must include:
- robots.txt — TanStack server route at
/robots.txtreturningUser-agent: * / Allow: / / Sitemap: <origin>/sitemap.xml. Drop-in:app/src/routes/robots.txt.ts. - sitemap.xml — TanStack server route at
/sitemap.xmlenumerating all public page routes. Drop-in:app/src/routes/sitemap.xml.ts. - Canonical URLs — every page route's
head()must includelinks: [{ rel: 'canonical', href: '<absolute URL>' }]. - Security headers — apply
applySecurityHeaders()to every response — see## Worker Securitybelow. - No trailing slash —
/pricing/must 301 to/pricing. Handle inserver.tsbefore the SSR handler.
These are not optional for deployed sites. The SEO audit
(references/seo.md#audit) checks them before deploy.
Worker Security
Every Worker must follow these constraints. Load
references/security.md#worker-hardening for the full rules.
- No global mutable state. Module-level variables are shared across requests in the same V8 isolate. Never store request-scoped data at module scope.
- Cryptographic randomness only. Use
crypto.randomUUID()andcrypto.getRandomValues(). NeverMath.random()for IDs, tokens, or nonces. - No hardcoded secrets. The platform injects auth via the outbound Worker.
Website code never handles platform tokens directly. For your OWN secrets (API
keys, etc.), set them with
higgsfield website secrets set <website_id> --name … --value …and read them server-side asbindings().SECRET_NAME— never hardcode them in source. - Security headers on every response. Apply
applySecurityHeaders()(frame-ancestors allowlist, no X-Frame-Options) — seereferences/security.md#worker-hardening. - Validate server function inputs.
createServerFninputs come from the client. Always validate shape and types before processing.