"Walk me through your CI" is a question about judgement: what you gate on, what you parallelise, how you get a preview to a reviewer, how you ship without fear and roll back in a minute. The pipeline below is the shape most Nuxt teams converge on; the quality gates page covers the test side, this page the delivery side.
nuxt preview or the .output artifact) → performance and accessibility budgets → preview deploy for pull requests → on the main branch: migrations, deploy, post-deploy smoke, release notes. Fast, cheap jobs first so failures return in minutes..output (or container image) moves from preview to staging to production, with runtimeConfig supplying per-environment values through NUXT_* variables; rebuilding per environment is a classic source of "worked in staging".actions/setup-node with cache: pnpm), Turborepo remote cache for task outputs, Playwright browsers cached by version, .nuxt/node_modules/.cache for Vite's dependency cache where safe.--shard=1/4) with merged reports; matrix builds only where the runtime matters (Node 24 and 26 for a library, one runtime for an app).staging deploys automatically from main; production requires an approval or a tag, uses environment-scoped secrets and concurrency groups so two deploys cannot interleave.NUXT_PUBLIC_*, secret scanning on pushes, rotation runbooks.retries: 2 in CI) with the flake rate tracked; a test that fails on main blocks merging until fixed or quarantined.permissions, dependency review on PRs, SBOM generation, container image scanning, provenance attestations for artifacts.A complete GitHub Actions workflow for a Nuxt app deployed to Cloudflare Workers with preview URLs:
name: CI
on:
pull_request:
push: { branches: [main] }
concurrency: { group: ci-${{ github.ref }}, cancel-in-progress: true }
permissions: { contents: read }
jobs:
checks:
runs-on: ubuntu-latest
strategy: { matrix: { task: [lint, typecheck, test] } }
steps:
- uses: actions/checkout@v7
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v7
with: { node-version-file: .nvmrc, cache: pnpm }
- run: pnpm install --frozen-lockfile
- run: pnpm ${{ matrix.task }}
build:
needs: checks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v7
with: { node-version-file: .nvmrc, cache: pnpm }
- run: pnpm install --frozen-lockfile
- run: pnpm build
env: { NUXT_PUBLIC_SITE_URL: https://preview.example.com }
- uses: actions/upload-artifact@v7
with: { name: output, path: .output, retention-days: 7 }
e2e:
needs: build
runs-on: ubuntu-latest
strategy: { fail-fast: false, matrix: { shard: [1, 2, 3, 4] } }
steps:
- uses: actions/checkout@v7
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v7
with: { node-version-file: .nvmrc, cache: pnpm }
- run: pnpm install --frozen-lockfile
- uses: actions/download-artifact@v8
with: { name: output, path: .output }
- run: pnpm exec playwright install --with-deps chromium
- run: pnpm exec playwright test --shard=${{ matrix.shard }}/4 # webServer runs `node .output/server/index.mjs`
- uses: actions/upload-artifact@v7
if: failure()
with: { name: playwright-report-${{ matrix.shard }}, path: playwright-report }
preview:
if: github.event_name == 'pull_request'
needs: build
runs-on: ubuntu-latest
permissions: { contents: read, pull-requests: write, id-token: write }
steps:
- uses: actions/checkout@v7
- uses: actions/download-artifact@v8
with: { name: output, path: .output }
- uses: cloudflare/wrangler-action@v4
id: deploy
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
command: versions upload --preview-alias pr-${{ github.event.number }}
- run: gh pr comment ${{ github.event.number }} --body "Preview: ${{ steps.deploy.outputs.deployment-url }}"
env: { GH_TOKEN: ${{ github.token }} }
deploy:
if: github.ref == 'refs/heads/main'
needs: [build, e2e]
runs-on: ubuntu-latest
environment: production # requires reviewer approval in repo settings
concurrency: { group: deploy-production, cancel-in-progress: false }
steps:
- uses: actions/checkout@v7
- uses: actions/download-artifact@v8
with: { name: output, path: .output }
- run: pnpm dlx drizzle-kit migrate # expand-only migrations, from the same commit
env: { DATABASE_URL: ${{ secrets.DATABASE_URL }} }
- uses: cloudflare/wrangler-action@v4
with: { apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}, command: deploy }
- run: curl --fail --retry 5 --retry-delay 3 https://example.com/api/ready
Rollback as a runbook, not an improvisation:
# Cloudflare Workers: list versions, roll back to the previous one (instant)
wrangler versions list
wrangler rollback <previous-version-id>
# Node/container platforms: redeploy the previous image or artifact
docker service update --image ghcr.io/team/shop:<previous-sha> shop
Decoupling deploy from release with a flag:
export function useFeature(name: string) {
const flags = useState<Record<string, boolean>>('flags', () => ({}))
return computed(() => flags.value[name] === true)
}
// flags come from runtimeConfig or a flag service, loaded once in a plugin; a bad release is turned off, not redeployed
NUXT_PUBLIC_API_BASE changes between staging and production, so someone rebuilds for each and tests one artifact while shipping another. Public runtime config is read from the environment at server start (and injected into the payload), so one build serves every environment; rebuild only for genuinely build-time differences.
Previews built from forked pull requests must not receive production secrets or database access; CI providers withhold secrets from forks by default, and previews should run against seeded staging data with basic auth in front.
Current Wrangler commands for preview aliases and rollbacks, actions/setup-node cache options and the current major versions of the actions used, and Playwright's sharding and report-merging commands.
"Fast, cheap gates first: install from cache, then lint, type check and unit tests as parallel jobs. Then one build whose .output is uploaded as the artifact everything else uses: Playwright sharded across four runners against the built server, performance and accessibility budgets, and a preview deployment per pull request with the URL commented on the PR so reviewers click instead of checking out. On main, the same artifact is promoted: expand-only migrations run first, the deploy follows with health checks and a concurrency group so deploys never interleave, and a readiness probe confirms it. Rollback is redeploying the previous version with one command, and feature flags decouple releasing from deploying. Secrets are environment-scoped with short-lived OIDC credentials, actions are pinned, and I watch pipeline duration, flake rate and the DORA metrics to keep it healthy."
Nothing yet. Notes filed here show up on this page and the section overview.
Package management and monorepos
pnpm as the default package manager — the content-addressable store, strict node_modules, lockfiles and frozen installs, catalogs, overrides and patches, workspaces and Turborepo task graphs, consuming Nuxt layers and internal packages, publishing with changesets and provenance, Node version management with Corepack, and supply-chain hygiene.
Developer experience and debugging
Nuxt DevTools and what it reveals, debugging SSR and Nitro code with breakpoints, source maps and the Node inspector, HMR and dev-server troubleshooting, environment files and per-environment config, reproducible local environments, onboarding in ten minutes, scripts and generators, and a debugging method for the classic Nuxt failures.