Full-Stack Apps
Use this skill when a Cloudflare Worker serves both frontend assets and backend routes.
Rendering decision
| Page type | Prefer | Notes |
|---|---|---|
| Marketing/docs/static content | Static assets | Lowest cost and operational complexity |
| Personalized dashboard | SSR or API + SPA | Fetch user data through bindings |
| Highly interactive app | SPA + API routes | Keep data APIs clean and cacheable where possible |
| Mixed site | Hybrid | Static shell with SSR islands or API-backed hydration |
Route layout pattern
export interface Env {
ASSETS: Fetcher;
DB: D1Database;
}
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
const url = new URL(request.url);
if (url.pathname.startsWith("/api/")) {
return api(request, env, ctx);
}
// Serve compiled frontend assets.
return env.ASSETS.fetch(request);
}
} satisfies ExportedHandler<Env>;
API route pattern
async function api(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === "/api/todos" && request.method === "GET") {
const todos = await env.DB.prepare(
"SELECT id, title, completed FROM todos ORDER BY created_at DESC LIMIT 100"
).all<Todo>();
return Response.json(todos.results);
}
return Response.json({ error: "not_found" }, { status: 404 });
}
Cache policy
- Static hashed assets: long cache TTL and immutable headers.
- HTML shell/SSR: short TTL or no-store if personalized.
- API responses: cache only when auth, tenant, and freshness rules are explicit.
- Never cache user-specific HTML or JSON without varying by user/session/tenant.
State placement
- D1: relational records for app data.
- Durable Objects: shared live state such as a room, document, cart, or session coordinator.
- R2: uploaded files, generated exports, media.
- KV: cached config, flags, denormalized public reads.
- Queues/Workflows: background jobs triggered by UI actions.
Review checklist
- Asset route cannot shadow API routes unexpectedly.
- CORS and authentication are enforced for APIs.
- SSR code does not assume Node server globals.
- Data-fetching runs in parallel where safe.
- Error responses are user-safe and structured.
- Build output path matches the
assets.directorybinding.