spiceflow
Spiceflow is a super simple, fast, and type-safe API and React Server Components framework for TypeScript. Works on Node.js, Bun, and Cloudflare Workers. ALWAYS load this skill BEFORE writing or editi
SKILL.mdsynced-skills/spiceflow/SKILL.md
GitHubSpiceflow
A project uses spiceflow if anything imports from spiceflow or spiceflow/react, or constructs new Spiceflow(). The API surface is small but very opinionated, and the opinions are NOT guessable. Every framework has its own answer for forms, pending state, error display, and redirects; spiceflow's answers are frequently different from Next.js and from React defaults. Writing spiceflow code from memory, or from what "looks like Next.js", produces wrong code every time.
How the docs work
Documentation lives at https://getspiceflow.com. Append .md to any page URL to get raw markdown. Fetch the docs index first if you have not read it this session:
curl -s https://getspiceflow.com/llms.txt
https://getspiceflow.com/llms-full.txt contains every doc in one file, and https://getspiceflow.com/docs.zip downloads all markdown files for local grepping.
Read every fetched doc completely, with no truncation. Never pipe to head, tail, or sed. Source code and runnable examples (example-* folders) live in the repo: https://github.com/remorses/spiceflow — read the code there when the docs are not enough.
ALWAYS read the feature doc before writing code
Before touching code for a feature, fetch its doc in full. One page per branch:
| Task | Fetch |
|---|---|
API routes, Zod validation, json(), typed errors, middleware, serveStatic, CORS, streaming/SSE, .onError(), listen(), Node adapters, Next.js mount, waitUntil, base path, class instances |
https://getspiceflow.com/api.md |
Typed fetch client (createSpiceflowFetch), WebMCP |
https://getspiceflow.com/fetch-client.md |
| OpenAPI generation | https://getspiceflow.com/openapi.md |
| MCP tools | https://getspiceflow.com/mcp.md |
| Tracing / OpenTelemetry | https://getspiceflow.com/tracing.md (Strada projects: https://getspiceflow.com/strada.md) |
Custom serialization (Date, Map, Set, BigInt) |
https://getspiceflow.com/custom-serialization.md |
RSC setup, Tailwind, shadcn, app entry, layouts, <Head> SEO, query params, "use client" components, code splitting, router, Link, redirects, ProgressBar, 404 pages |
https://getspiceflow.com/react.md |
Loaders, useLoaderData, streaming with use(), forms, server actions, parseFormData, useActionState, ErrorBoundary |
https://getspiceflow.com/react-data.md |
SpiceflowRegister, knownPaths, multi-app workspaces, circular TS7022 errors |
https://getspiceflow.com/type-safety.md |
| Federation / remote components | https://getspiceflow.com/federation.md |
| Auth middleware, proxying, cookies, graceful shutdown | https://getspiceflow.com/middleware-patterns.md |
| Securing actions and routes | https://getspiceflow.com/security.md |
| Migrating from Remix / React Router | https://getspiceflow.com/migrate-from-remix.md |
| Cloudflare Workers (setup, bindings, KV caching, edge cache) | https://getspiceflow.com/cloudflare.md |
| Deploy skew, cross-deployment behavior | https://getspiceflow.com/deployment-skew.md |
| Service bindings | https://getspiceflow.com/service-bindings.md |
| Docker | https://getspiceflow.com/docker.md |
Dependency crashes with useState is undefined at startup |
https://github.com/remorses/spiceflow/blob/main/docs/use-client-trap.md |
Testing spiceflow apps
Before writing any vitest tests for a spiceflow app, ALWAYS read the testing guide first:
curl -s https://getspiceflow.com/testing.md
Reference examples: example-vitest (API routes, pages, actions, DI, tracing spans) and example-vitest-cloudflare (tests inside workerd with D1 and KV).
Non-negotiable rules
These are the landmines that break apps when guessed. Each links to its full doc.
- Always use
Linkfromspiceflow/react, never raw<a>, for links.Linkauto-prepends the Vitebasepath; never prepend it manually. Rawfetch()andResponse.redirect()still need manual base handling. → navigation <Head>is server-only. It records children during the RSC render; a'use client'module never runs there, so a<Head>inside one contributes nothing (and importing it fails the build). Put<Head>in.page()/.layout()handlers. Usedocument.titlein an effect for client-side title changes. → pages & layouts- Always
throw redirect(...), neverreturn redirect(...). Applies to.page(),.layout(),.loader(),.get(),.post(), server actions, and middleware. → navigation - Never call
router.refresh()after a server action. Successful actions already re-run loaders and reconcile the page. All navigation and refresh methods are fire-and-forget; never await a navigation commit inside a React form action (it can deadlock). → forms & actions - Only components cross the
'use client'boundary. A server component that imports a string or object (e.g. a class-name constant) from a'use client'file gets a client reference, not the value, and renders it as a function source. Put shared constants in a plain module both sides import. - Server actions and API routes are public endpoints. CSRF Origin checks do not authenticate the caller; every mutating action must read and verify a session or token itself via
getActionRequest(). → security - Typed fetch client: use
:parampaths with aparamsobject (never interpolate IDs), return plain objects orjson(...)from handlers (neverreturn new Response(...)), passbodyas a plain object, and check results withinstanceof Error. → fetch client - Pass an OTel tracer in production apps (
new Spiceflow({ tracer })) so handlers getspanandtraceron the context. → tracing - One spiceflow copy per monorepo. Mismatched or duplicated versions cause
Types have separate declarations of a private property. Fix withpnpm update -r spiceflowthenpnpm dedupe(ornpm update spiceflow --workspaces/bun update -r spiceflow). → fetch client - Circular TS7022 errors happen when a
SpiceflowRegister-typed API (router.href(),createSpiceflowFetch(),useLoaderData()) appears in a handler return value in the app entry. JSX,throw, event handlers, and separate files are always safe. → type safety - Always 301
wwwto the apex in one hop on new custom-domain sites. Pointwww.example.comat the same Cloudflare worker (custom_domain). Redirectwww→https://example.com+ path + query with status 301. Never leave www on Vercel/Pages. Never use 307. Never chainhttp://www→https://www→ apex.
.use(({ request }, next) => {
const url = new URL(request.url)
if (!url.hostname.startsWith('www.')) return next()
url.hostname = url.hostname.slice('www.'.length)
url.protocol = 'https:'
throw redirect(url.toString(), { status: 301 })
})