# Patterns ## API Routes ```typescript // functions/api/todos/[id].ts export const onRequestGet: PagesFunction = async ({ env, params }) => { const todo = await env.DB.prepare('SELECT * FROM todos WHERE id = ?').bind(params.id).first(); if (!todo) return new Response('Not found', { status: 404 }); return Response.json(todo); }; export const onRequestPut: PagesFunction = async ({ env, params, request }) => { const body = await request.json(); await env.DB.prepare('UPDATE todos SET title = ?, completed = ? WHERE id = ?') .bind(body.title, body.completed, params.id).run(); return Response.json({ success: true }); }; // Also: onRequestDelete, onRequestPost ``` ## Auth Middleware ```typescript // functions/_middleware.ts const auth: PagesFunction = async (context) => { if (context.request.url.includes('/public/')) return context.next(); const authHeader = context.request.headers.get('Authorization'); if (!authHeader?.startsWith('Bearer ')) { return new Response('Unauthorized', { status: 401 }); } try { const payload = await verifyJWT(authHeader.substring(7), context.env.JWT_SECRET); context.data.user = payload; return context.next(); } catch (err) { return new Response('Invalid token', { status: 401 }); } }; export const onRequest = [auth]; ``` ## CORS ```typescript // functions/api/_middleware.ts const corsHeaders = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization' }; export const onRequest: PagesFunction = async (context) => { if (context.request.method === 'OPTIONS') { return new Response(null, {headers: corsHeaders}); } const response = await context.next(); Object.entries(corsHeaders).forEach(([k, v]) => response.headers.set(k, v)); return response; }; ``` ## Form Handling ```typescript // functions/api/contact.ts export const onRequestPost: PagesFunction = async ({ request, env }) => { const formData = await request.formData(); await env.QUEUE.send({name: formData.get('name'), email: formData.get('email')}); return new Response('

Thanks!

', { headers: { 'Content-Type': 'text/html' } }); }; ``` ## Background Tasks ```typescript export const onRequestPost: PagesFunction = async ({ request, waitUntil }) => { const data = await request.json(); waitUntil(fetch('https://api.example.com/webhook', { method: 'POST', body: JSON.stringify(data) })); return Response.json({ queued: true }); }; ``` ## Error Handling ```typescript // functions/_middleware.ts const errorHandler: PagesFunction = async (context) => { try { return await context.next(); } catch (error) { console.error('Error:', error); if (context.request.url.includes('/api/')) { return Response.json({ error: error.message }, { status: 500 }); } return new Response(`

Error

${error.message}

`, { status: 500, headers: { 'Content-Type': 'text/html' } }); } }; export const onRequest = [errorHandler]; ``` ## Caching ```typescript // functions/api/data.ts export const onRequestGet: PagesFunction = async ({ env, request }) => { const cacheKey = `data:${new URL(request.url).pathname}`; const cached = await env.KV.get(cacheKey, 'json'); if (cached) return Response.json(cached, { headers: { 'X-Cache': 'HIT' } }); const data = await env.DB.prepare('SELECT * FROM data').first(); await env.KV.put(cacheKey, JSON.stringify(data), {expirationTtl: 3600}); return Response.json(data, {headers: {'X-Cache': 'MISS'}}); }; ``` ## Smart Placement for Database Apps Enable Smart Placement for apps with D1 or centralized data sources: ```jsonc // wrangler.jsonc { "name": "global-app", "placement": { "mode": "smart" }, "d1_databases": [{ "binding": "DB", "database_id": "your-db-id" }] } ``` ```typescript // functions/api/data.ts export const onRequestGet: PagesFunction = async ({ env }) => { // Smart Placement optimizes execution location over time // Balances user location vs database location const data = await env.DB.prepare('SELECT * FROM products LIMIT 10').all(); return Response.json(data); }; ``` **Best for**: Read-heavy apps with D1/Durable Objects in specific regions. **Not needed**: Apps without data locality constraints or with evenly distributed traffic. ## Framework Integration **Supported** (2026): SvelteKit, Astro, Nuxt, Qwik, Solid Start ```bash npm create cloudflare@latest my-app -- --framework=svelte ``` ### SvelteKit ```typescript // src/routes/+page.server.ts export const load = async ({ platform }) => { const todos = await platform.env.DB.prepare('SELECT * FROM todos').all(); return { todos: todos.results }; }; ``` ### Astro ```astro --- const { DB } = Astro.locals.runtime.env; const todos = await DB.prepare('SELECT * FROM todos').all(); ---
    {todos.results.map(t =>
  • {t.title}
  • )}
``` ### Nuxt ```typescript // server/api/todos.get.ts export default defineEventHandler(async (event) => { const { DB } = event.context.cloudflare.env; return await DB.prepare('SELECT * FROM todos').all(); }); ``` **⚠️ Framework Status** (2026): - ✅ **Supported**: SvelteKit, Astro, Nuxt, Qwik, Solid Start - ❌ **Deprecated**: Next.js (`@cloudflare/next-on-pages`), Remix (`@remix-run/cloudflare-pages`) For deprecated frameworks, see [gotchas.md](./gotchas.md#framework-specific) for migration options. [Framework Guides](https://developers.cloudflare.com/pages/framework-guides/) ## Monorepo Dashboard → Settings → Build → Root directory. Set to subproject (e.g., `apps/web`). ## Best Practices **Performance**: Exclude static via `_routes.json`; cache with KV; keep bundle < 1MB **Security**: Use secrets (not vars); validate inputs; rate limit with KV/DO **Workflow**: Preview per branch; local dev with `wrangler pages dev`; instant rollbacks in Dashboard