Shipping is half of fullstack. "How is it deployed, how do you know it is healthy, how do you roll back" are the questions that reveal whether someone has operated what they built. Nuxt's answer starts with a Nitro preset and ends with the same operational discipline any service needs; this app itself is the static case, deployed to Cloudflare's asset hosting with wrangler.
node-server (default; a self-contained .output/server/index.mjs, no node_modules, ideal in a small Docker image), node-cluster (multi-process), cloudflare_module (Workers with static assets; bindings for KV, D1, R2, Queues, Durable Objects via event.context.cloudflare.env; Hyperdrive for Postgres; nodejs_compat for Node APIs; CPU-time limits), vercel (serverless functions, ISR support, vercel-edge for edge), netlify, aws-lambda, azure, deno-deploy, bun, static (nuxt generate; this app, on Cloudflare asset hosting), and more; most platforms are auto-detected, or set nitro.preset/NITRO_PRESET. nuxt build --preset overrides.runtimeConfig values are read from NUXT_* environment variables when the server starts; secrets come from the platform's secret store (Docker secrets, Cloudflare secrets, Vercel env), never from a committed .env; compatibilityDate pins behaviour; one build serves every environment./api/health (liveness: process up) and optionally /api/ready (readiness: database reachable) for orchestrators; graceful shutdown on SIGTERM (stop accepting, finish in-flight, close pools; Nitro's close hook), timeouts on everything.requestId, route, duration and user id where allowed, no secrets or PII; consola (Nitro's logger) or pino; a request-id middleware that also returns x-request-id; log aggregation (Loki, CloudWatch, Datadog) with retention.@sentry/nuxt (client and server, source maps uploaded at build, releases tagged with the git SHA), performance tracing (OpenTelemetry where the platform supports it), uptime checks from outside, Core Web Vitals RUM (web-vitals → your endpoint or the vendor), dashboards for p95 latency, error rate, cache hit ratio, and alerts with owners.swr pages on a CDN; the app as a server or edge deployment; both from one repository via route rules. A CDN (Cloudflare, Fastly) in front of a Node server for TLS, caching, WAF and rate limiting is normal..output into node:24-alpine), non-root user, NITRO_PORT/HOST, a HEALTHCHECK, resource limits; images are tiny because .output bundles dependencies.runtimeConfig overrides; nuxt preview to run the production build locally.Node in a container, with health, shutdown and structured logs:
FROM node:24-alpine AS build
WORKDIR /app
RUN corepack enable
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .
RUN pnpm nuxt build
FROM node:24-alpine
WORKDIR /app
ENV NODE_ENV=production NITRO_PORT=3000 HOST=0.0.0.0
USER node
COPY --from=build --chown=node:node /app/.output ./.output
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s CMD wget -qO- http://127.0.0.1:3000/api/health || exit 1
CMD ["node", ".output/server/index.mjs"]
export default defineEventHandler(() => ({ status: 'ok', version: useRuntimeConfig().public.version, uptime: process.uptime() }))
export default defineEventHandler(async (event) => {
try {
await useDb().execute(sql`select 1`)
return { status: 'ready' }
}
catch {
throw createError({ status: 503, statusText: 'Database unavailable' })
}
})
export default defineNitroPlugin((nitroApp) => {
const log = (level: string, msg: string, extra: Record<string, unknown> = {}) => console.log(JSON.stringify({ level, msg, time: new Date().toISOString(), ...extra }))
nitroApp.hooks.hook('request', (event) => {
event.context.requestId = getHeader(event, 'x-request-id') ?? crypto.randomUUID()
event.context.startedAt = performance.now()
setResponseHeader(event, 'x-request-id', event.context.requestId)
})
nitroApp.hooks.hook('afterResponse', (event) => {
log('info', 'request', { requestId: event.context.requestId, method: event.method, path: event.path, status: getResponseStatus(event), ms: Math.round(performance.now() - event.context.startedAt) })
})
nitroApp.hooks.hook('error', (error, { event }) => {
log('error', 'unhandled', { requestId: event?.context.requestId, path: event?.path, error: String(error) })
})
nitroApp.hooks.hook('close', async () => {
log('info', 'shutting down: closing pools')
await useDb().$client.end?.() // drain connections on SIGTERM
})
})
Cloudflare Workers with bindings, and the release pipeline order:
{
"name": "shop",
"main": "./.output/server/index.mjs",
"compatibility_date": "2026-09-01",
"compatibility_flags": ["nodejs_compat"],
"assets": { "directory": "./.output/public", "binding": "ASSETS" },
"kv_namespaces": [{ "binding": "CACHE", "id": "..." }],
"r2_buckets": [{ "binding": "UPLOADS", "bucket_name": "shop-uploads" }],
"hyperdrive": [{ "binding": "HYPERDRIVE", "id": "..." }],
"observability": { "enabled": true }
}
export default defineNuxtConfig({
nitro: {
preset: 'cloudflare_module',
cloudflare: { deployConfig: true, nodeCompat: true },
storage: { cache: { driver: 'cloudflare-kv-binding', binding: 'CACHE' }, uploads: { driver: 'cloudflare-r2-binding', binding: 'UPLOADS' } },
},
sourcemap: { server: true, client: 'hidden' }, // uploaded to Sentry, not served
modules: ['@sentry/nuxt/module'],
})
- run: pnpm nuxt build
- run: pnpm drizzle-kit migrate # expand-only migrations, before traffic switches
env: { DATABASE_URL: ${{ secrets.DATABASE_URL }} }
- run: npx wrangler deploy # atomic version switch; `wrangler rollback` restores the previous version
- run: npx @sentry/cli releases finalize ${{ github.sha }}
Runtime configuration the same way everywhere:
# Docker / VM
NUXT_DATABASE_URL=postgres://... NUXT_SESSION_PASSWORD=... node .output/server/index.mjs
# Cloudflare
wrangler secret put NUXT_SESSION_PASSWORD
# Vercel / Netlify: project environment variables named NUXT_*
Baking API_URL into the client bundle at build time means a separate build for staging and production and a real risk of shipping the wrong one. runtimeConfig plus NUXT_* variables lets one artifact run everywhere; only truly public build-time constants belong in vite.define.
Deploying code that expects a new column before the migration ran is an outage; running a destructive migration before old instances stop is another. Expand first (add columns, backfill), deploy, then contract in a later release once nothing reads the old shape.
The current preset names (cloudflare_module versus cloudflare-pages), Cloudflare's Nuxt guide for Workers with static assets, and NuxtHub's self-hosting instructions after the Admin sunset.
NUXT_* variables, hit /api/health and /api/ready, then stop the database and watch readiness fail while liveness stays green.wrangler rollback or redeploying the previous image, and time the recovery."The Nitro preset decides the runtime: a self-contained Node server in a small Docker image behind a CDN when I need long-lived connections or heavy CPU; Cloudflare Workers with KV, R2 and Hyperdrive bindings when I want scale-to-zero at the edge; Vercel or Netlify when the team lives there; static for marketing and docs. One build serves every environment because configuration arrives through NUXT_* variables at start-up and secrets come from the platform's store. Operationally: liveness and readiness endpoints, graceful shutdown on SIGTERM, JSON logs with a request id that is also returned to the client, Sentry for errors on both sides with source maps and release tags, RUM for Core Web Vitals, and alerts on error rate and p95. Releases build once, run expand-only migrations before traffic switches, deploy with zero downtime, and keep the previous version one command away for rollback."
"NuxtHub started as a Cloudflare-specific hosting layer with a hosted Admin; the Admin was sunset at the end of 2025 and its CLI deployments stopped in February 2026, and the project pivoted to self-hosting and a multi-vendor direction. So on Cloudflare today I would deploy with Wrangler directly: the cloudflare_module preset, wrangler.jsonc with KV, R2, D1 or Hyperdrive bindings surfaced through event.context.cloudflare.env or Nitro storage drivers, Workers' own CI or GitHub Actions, and wrangler rollback for recovery, optionally keeping the NuxtHub module in self-hosted mode for its database, KV and blob helpers."
Nothing yet. Notes filed here show up on this page and the section overview.
API design and integration
REST conventions that make an API predictable — resources, methods, status codes, a consistent error shape, pagination, filtering, versioning, idempotency keys, conditional requests — plus OpenAPI from Nitro, when tRPC, oRPC or GraphQL earn their complexity, the backend-for-frontend pattern, proxies, webhooks in and out, API keys and rate limits.
Architecture & system design
What senior frontend and fullstack design rounds actually assess, the shape of a good answer, and how the pages on the design method, component and design-system architecture, application structure, rendering and data architecture, worked designs and decision records fit together.