CI/CD for a vibe-coded project is the difference between shipping a demo URL and running a product you can iterate on without fear. AI builders and Cursor sessions move fast — but without lint, test, build, and deploy gates on every push, you redeploy the same TypeScript error five times and wonder why Vercel hates you. This tutorial walks through a production-ready GitHub Actions pipeline for Next.js and React repos born from vibe coding: sample YAML, secrets management, Vercel deploy hooks, and the failure patterns AI codegen leaves behind. Updated November 2026.

Before you wire CI, confirm the app actually builds locally — start with deploy a vibe-coded app to Vercel if you have not shipped yet. Pair this guide with write tests for AI-generated code and how to review AI-generated code so your pipeline catches what prompt-and-pray misses.
Table of contents
- Why CI/CD matters for vibe-coded repos
- Pipeline overview: lint, test, build, deploy
- Sample GitHub Actions workflow (Next.js + Vercel)
- Secrets management for AI-built apps
- Branch strategy and preview deploys
- Common CI failures in AI-generated Next.js code
- Hardening checklist before you trust the bot
- FAQ
Why CI/CD matters for vibe-coded repos
Vibe coding optimizes for speed: describe a feature, accept a diff, preview in the browser. That loop breaks down at team scale and at production scale because AI output is probabilistic. The same prompt on Tuesday produces different import paths than on Monday. Without automated gates, you merge broken code because the preview looked fine in dev mode with hot reload masking a failed production build.
Continuous integration (CI) runs on every push or pull request: lint, typecheck, unit tests, and a production build. Continuous deployment (CD) promotes passing builds to staging or production — often via Vercel, Netlify, or Cloudflare Pages connected to GitHub. For solo builders, CI is your unpaid senior engineer who refuses to merge red builds.
| Without CI/CD | With CI/CD |
|---|---|
| Deploy broken builds; debug in production | Catch failures before users see them |
| Forget to run tests after AI refactors | Tests run automatically on every diff |
Secrets leak in committed .env files | Secrets live in GitHub/Vercel only; CI uses masked vars |
| No audit trail of what shipped when | Git history + workflow logs = reproducible deploys |
Preview URLs only from local npm run dev | PR previews on real infra with prod-like env |
If you exported from Lovable, Bolt, or Replit, you likely already have a GitHub repo. Adding Actions takes 30–60 minutes once and saves hours per week. Align with the vibe coding starter stack 2026 (Cursor + Supabase + Vercel) so your pipeline matches your hosting choice.
Pipeline overview: lint, test, build, deploy
A sensible default for Next.js App Router projects:
- Lint — ESLint catches unused vars, hook violations, and accessibility issues AI ignores
- Typecheck —
tsc --noEmitfails on implicit any and broken imports stricter than dev server - Unit tests — Vitest or Jest on critical paths (auth helpers, API route handlers, utils)
- Build —
next buildmirrors Vercel; catches SSR and env errors local dev hides - Deploy — Vercel Git integration (simplest) or GitHub Action calling Vercel CLI with tokens
You can split jobs for speed: lint + typecheck in parallel with test; build depends on both. Cache node_modules via actions/cache keyed on lockfile hash. Pin Node version (node-version: '20') — AI often assumes latest Node APIs.
| Stage | Typical command | What AI code breaks |
|---|---|---|
| Lint | npm run lint | Unused imports, missing deps in useEffect |
| Typecheck | npx tsc --noEmit | Any types, wrong prop shapes, bad generics |
| Test | npm test | Untested edge cases, mocked env gaps |
| Build | npm run build | Missing env at build time, dynamic import errors |
| Deploy | Vercel / vercel --prod | Wrong root directory, missing secrets |
Sample GitHub Actions workflow (Next.js + Vercel)
Create .github/workflows/ci.yml in your repo root. This example runs on push to main and on all pull requests. Deploy step uses Vercel CLI; alternatively, skip deploy in Actions and let Vercel’s GitHub app deploy when CI passes (recommended for beginners).
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- name: Lint
run: npm run lint
- name: Typecheck
run: npx tsc --noEmit
- name: Unit tests
run: npm test -- --run
env:
CI: true
- name: Production build
run: npm run build
env:
NEXT_PUBLIC_SITE_URL: https://example.com
# Add build-time public env vars here or use dummy values for CI
deploy-preview:
needs: quality
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- name: Deploy Preview to Vercel
run: npx vercel deploy --token=${{ secrets.VERCEL_TOKEN }}
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
deploy-production:
needs: quality
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- name: Deploy Production to Vercel
run: npx vercel deploy --prod --token=${{ secrets.VERCEL_TOKEN }}
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
Simpler path: Delete the deploy jobs and rely on Vercel’s native GitHub integration. Keep the quality job only — Vercel will not deploy if you add “Required status checks” on the quality job in branch protection. That pattern separates “does it build?” (Actions) from “ship it” (Vercel).
Add a package.json scripts block if AI omitted them:
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"test": "vitest"
}
For monorepos (Turborepo), set working-directory on steps or use turbo run lint test build. AI loves nested apps/web without documenting root — match Vercel’s Root Directory setting.

Secrets management for AI-built apps
AI agents routinely suggest committing .env files or hardcoding Stripe keys in source. CI/CD forces secrets into the right places.
Where secrets live
- GitHub Actions secrets — Repository Settings → Secrets and variables → Actions. Use for
VERCEL_TOKEN, deploy keys, optional E2E credentials - Vercel environment variables — Project Settings → Environment Variables. Production, Preview, and Development scopes. Server secrets never get
NEXT_PUBLIC_prefix - Local only —
.env.localgitignored; commit.env.examplewith placeholder names only
Required secrets for Vercel CLI deploy from Actions
| Secret name | Where to get it | Notes |
|---|---|---|
VERCEL_TOKEN | Vercel → Account Settings → Tokens | Scope to deploy only; rotate if leaked |
VERCEL_ORG_ID | .vercel/project.json after vercel link | Not sensitive but required for CLI |
VERCEL_PROJECT_ID | Same file | Maps repo to Vercel project |
For Supabase, Clerk, or Stripe: store API keys in Vercel env vars, not GitHub Actions, unless tests need them. In CI build step, use dummy NEXT_PUBLIC_* values so next build does not fail on missing URLs — document real values only in Vercel.
Preventing leaks in CI logs
GitHub masks secrets in logs when referenced as ${{ secrets.NAME }}. Never echo secrets. Avoid printing full env in debug steps. If AI added console.log(process.env) anywhere, remove it before first CI run — that is a common accidental exposure vector.
Run gitleaks or GitHub secret scanning on the repo. AI-generated commits sometimes paste keys in comments or test fixtures. Review diffs with the same rigor as production code — see how to review AI-generated code.
Branch strategy and preview deploys
Recommended flow for vibe-coded solo projects scaling to collaborators:
main— production; protected; requires passing CI- Feature branches —
feat/add-billing, one AI session per branch when possible - Pull requests — even solo: PR triggers CI + Vercel Preview URL for sharing
- Hotfix — short-lived branch from
main, merge after CI green
Enable branch protection on main: require status checks from quality job, require PR (optional for solo but builds habit), disallow force push. AI agents that auto-commit to main bypass your safety net — configure Cursor or Claude Code to work on branches.
Preview deploys let you test auth callbacks, webhooks, and Supabase RLS with staging credentials. Set Preview env vars in Vercel to a staging Supabase project so vibe coding experiments do not corrupt production rows.
Common CI failures in AI-generated Next.js code
These show up repeatedly in vibe-coded repos. Fix locally, push, watch CI go green.
| CI error | Root cause (AI pattern) | Fix |
|---|---|---|
Module not found: Can't resolve 'X' | Import added without npm install | Install package; commit lockfile; run npm ci locally |
| TypeScript errors only in CI | Local skips tsc; strict mode in CI | Run tsc --noEmit before push; fix types, not @ts-ignore |
next build — env undefined | NEXT_PUBLIC_* used at build time, missing in Actions env | Add dummy vars to workflow build step + real vars in Vercel |
ESLint react-hooks/exhaustive-deps | AI omitted dependency array entries | Fix effect deps; do not disable rule globally |
| Tests fail — fetch / network | Tests hit real APIs without mocks | Mock Supabase/Stripe; use MSW for route handlers |
| Edge runtime + Node API | export const runtime = 'edge' with fs or bcrypt | Switch route to Node runtime or swap library |
| Out of memory on build | Giant images imported statically | Use next/image remote patterns; compress assets |
ERESOLVE peer dependency | AI pinned React 19 with lib expecting 18 | Align versions in package.json; one major line |
| Playwright/E2E timeout | CI has no server running | Start app in background step or use Vercel preview URL |
Flaky tests: AI-generated tests often use Date.now() or random IDs without seeding. Prefer deterministic fixtures. See write tests for AI-generated code for Vitest patterns on utils and API routes without slowing vibe coding.
False green locally: Developers run next dev but never next build. Add a pre-push hook or habit: npm run build && npm test before opening PR. CI should not be the first time you compile for production.
Lockfile drift: AI runs npm install package without committing package-lock.json. CI uses npm ci which fails on mismatch. Always commit lockfiles after dependency changes.
Hardening checklist before you trust the bot
.github/workflows/ci.ymlcommitted; runs on PR and push to main- Branch protection requires CI pass before merge
.env.exampledocuments all env var names; no secrets in git history- Vercel env vars set for Production and Preview scopes
npm run lint,tsc --noEmit,npm test,npm run buildall pass locally- At least smoke tests on auth and one API route — per testing guide
- Code review checklist applied to AI diffs — escaping, auth, error handling
- Optional: Dependabot or Renovate for dependency updates AI forgets
Once CI is green, deployment becomes boring — the goal. Your next iteration is prompt chains for multi-file refactors with confidence that regressions surface in minutes, not after a user emails you.
FAQ
Do I need GitHub Actions if Vercel already builds my app?
Vercel builds on push, but it may not run your test suite or custom lint rules unless configured. A dedicated CI job runs tests and typecheck explicitly; you can block merges until CI passes even if Vercel would have deployed. Many teams use Actions for quality gates and Vercel for hosting only.
How do I run CI on a private repo for free?
GitHub Actions includes free minutes for private repos (limits vary by plan). Public repos get generous free usage. Lint + test + build on a small Next.js app typically consumes a few minutes per run. Cache dependencies to cut time and cost.
Should I deploy to production from GitHub Actions or Vercel Git integration?
For most vibe coders, Vercel Git integration is simpler: connect repo, set env vars, auto-deploy on merge to main. Use Actions deploy jobs only if you need custom steps (migrations, Slack notifications, multi-target deploys). Never duplicate deploy paths without meaning to ship twice.
CI fails on Supabase env vars I only set in Vercel — why?
The GitHub Actions build step runs on GitHub servers, not Vercel. It does not see Vercel env vars unless you duplicate them as GitHub secrets or use placeholder public vars for build-only. Server secrets are injected at runtime on Vercel, not during GitHub’s next build — structure code so server keys are not required at compile time.
Soft CTA: Open your vibe-coded repo tonight. Add the sample workflow, fix the first red build, and protect main. Tomorrow’s AI prompt ships through a pipeline that catches mistakes before your users do. If you have not deployed yet, start with deploy to Vercel, then come back and wire CI — ship first, automate second, but do not skip automate.
