Chapter 14: CI/CD (GitHub Actions & Vercel)
Welcome to Part Six: DevOps and Quality Assurance. In previous chapters, we've built a fully functional SAAS application covering all core features from database (Drizzle), authentication (better-auth), payments (Stripe) to operations (React Email). Now, it's time to ensure we can deliver these features to our users safely, reliably, and quickly...
Chapter 14: CI/CD (GitHub Actions & Vercel)
Welcome to Part Six: DevOps and Quality Assurance. In previous chapters, we've built a fully functional SAAS application covering all core features from database (Drizzle), authentication (better-auth), payments (Stripe) to operations (React Email). Now, it's time to ensure we can deliver these features to our users safely, reliably, and quickly.
For developers coming from a Python (Django/Flask) background, "deployment" might mean a series of complex manual or semi-automated operations: configuring Nginx, Gunicorn/uWSGI, setting up systemd services, managing environment variables on a VPS, running manage.py collectstatic, manually executing database migrations... This is a fragile and time-consuming process.
The Next.js and Vercel ecosystem completely overturns this model. In this chapter, we'll explore the DevOps workflow for modern full-stack applications and demonstrate how to leverage Vercel and GitHub Actions to create a CI/CD pipeline that offers both one-click deployment and enterprise-grade quality assurance.
14.1. Vercel: From Git to Global Deployment
Vercel (Next.js's parent company) core value proposition is: a seamless deployment experience optimized for modern web, starting from Git.
All you need to do is:
- Create a new project on the Vercel platform.
- Link it to your GitHub (or GitLab/Bitbucket) repository.
That's it! From this moment on, Vercel automatically handles everything for you:
- Automatic Build and Deployment: When you
git pushto themain(ormaster) branch, Vercel automatically pulls your code, detects it's a Next.js 15+ project, runspnpm build, and deploys it to its global Edge Network. - Global CDN: Your static assets (JS, CSS, images) are automatically cached on a global CDN, and users access from the nearest node.
- Serverless Functions: Your Server Actions and Route Handlers (API routes) are automatically deployed as Serverless Functions (or Vercel's Edge Functions), achieving elastic scaling.
- Data Caching: Vercel's infrastructure is deeply integrated with Next.js's data cache.
- Preview Deployments: This is Vercel's "killer feature." When you create a Pull Request (PR) for any branch (e.g.,
feat/new-dashboard), Vercel will automatically build and deploy a separate preview environment for this PR. You'll get a unique URL (e.g.,my-saas-pr-123.vercel.app) that you can share with team members, designers, and product managers for review. This completely eliminates the age-old problem of "it works on my machine."
This "GitOps" model is a mindset shift for Python developers. You no longer need to manage servers; you only need to manage your Git repository. Vercel handles all the complex work from code to global deployment.
14.2. [Deep Dive] Enterprise-Grade CI/CD Pipeline
Vercel's default workflow (main branch -> production) is great for small projects, but for a serious SAAS application with paying users, it's not enough. We cannot allow unverified code to be merged directly to main and immediately deployed to production.
We need Quality Assurance. We need a Deployment Gate.
This is where GitHub Actions (CI) and Vercel (CD) work together.
- CI (Continuous Integration): Handled by GitHub Actions. Its responsibility is code quality verification. It runs before code is allowed to deploy.
- CD (Continuous Deployment): Handled by Vercel. Its responsibility is building and delivering the application.
An enterprise-grade CI/CD pipeline looks like this:
- Development (Dev): Developers create a new branch locally, e.g.,
feat/improve-checkout. - Push: Developers push the branch to GitHub.
- CI Run (Feature Branch): GitHub Actions automatically detects the push and runs a CI job on that branch (see section 14.3), which executes:
pnpm install(install dependencies)pnpm lint(check code style and errors with Biome)pnpm typecheck(check types with TypeScript compiler)pnpm test(run unit and integration tests)
- Pull Request: After CI passes, developers create a PR to merge
feat/improve-checkoutintomain. - Preview Deployment (Vercel CD): Vercel immediately detects this PR and builds a preview deployment for the
feat/improve-checkoutbranch, posting an accessible URL in the PR comments. - CI Run (PR): GitHub Actions runs all checks again in the PR context and reports "pass/fail" status to the PR. This is a mandatory gate for merging.
- Review:
- Code Review: Other team members review the code.
- Feature Review: Product managers and QA (Quality Assurance) click the preview URL provided by Vercel to manually test new features in a real environment.
- Merge: After all checks pass and reviews are approved, the PR is merged into the
mainbranch. - Production Deployment (Vercel CD): Vercel detects new commits on the
mainbranch and automatically triggers production deployment. - Live: Within minutes, your new feature is live globally.
In this workflow, GitHub Actions acts as the "gatekeeper," ensuring only high-quality code enters the main branch; while Vercel acts as the "delivery person," responsible for safely and quickly delivering code to users (whether preview or production environments).
14.3. [Code Analysis]: Configuring GitHub Actions
To implement the CI workflow from section 14.2, we only need to add a .github/workflows/ci.yml file in the project root.
File 1: .github/workflows/ci.yml
This YAML file defines all the steps our "gatekeeper" needs to execute.
# .github/workflows/ci.yml
name: CI Checks # Workflow name
# 1. Trigger conditions
on:
# Trigger on push to any branch (for feature branches)
push:
branches:
- "**" # Match all branches
- "!main" # But exclude main, because main is handled by Vercel
# Trigger on creating or updating Pull Requests (for merge gate)
pull_request:
branches:
- main # Only care about PRs targeting main branch
# 2. Job definitions
jobs:
# We define a job named 'ci'
ci:
# Run environment: use latest Ubuntu
runs-on: ubuntu-latest
# 3. Steps
steps:
# Step 1: Checkout code
- name: Checkout code
uses: actions/checkout@v4
# Step 2: Install pnpm
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 8 # Specify pnpm version
# Step 3: Install Node.js (e.g., 20.x)
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm' # Automatically set up caching for pnpm, speeding up subsequent builds
# Step 4: Install dependencies
# Use --frozen-lockfile to ensure CI uses dependencies exactly matching pnpm-lock.yaml
- name: Install dependencies
run: pnpm install --frozen-lockfile
# Step 5: Run Lint (using Biome)
# Check code format and potential errors
- name: Run Lint (Biome)
run: pnpm lint
# Step 6: Run type check (TypeScript)
# --noEmit only checks types, doesn't generate JS files
- name: Run Typecheck (TypeScript)
run: pnpm typecheck
# Step 7: Run tests (e.g., Vitest)
# Your 'test' script should be configured to run in CI mode (e.g., non-watch mode)
- name: Run Tests
run: pnpm test --ciConfiguration Breakdown:
on: [push, pull_request]: We trigger on bothpushandpull_request.pushcovers immediate feedback for all feature branches, whilepull_requestis the final gate before merging tomain.uses: pnpm/action-setup@v3anduses: actions/setup-node@v4: This is best practice for configuring pnpm and Node.js environments.cache: 'pnpm': This line is crucial. It automatically caches thepnpm store, meaning GitHub Actions doesn't need to re-download all dependency packages on every run, greatly speeding up CI execution (typically from several minutes to under 30 seconds).pnpm lint,pnpm typecheck,pnpm test: These commands correspond to scripts defined in ourpackage.json. If any script fails (returns a non-zero exit code), the entire CI job fails, and GitHub blocks PR merging (if you've enabled "branch protection rules" in your repository settings).
Chapter 14 Summary
In this chapter, we built modern DevOps infrastructure for our SAAS application.
- Mindset Shift: We bid farewell to the complex manual deployments of the Python stack, embracing the GitOps workflow provided by Vercel. Vercel treats the Git repository as the "single source of truth," automatically handling everything from code to global deployment.
- Enterprise-Grade Workflow: We recognized that while Vercel's default deployment is simple, it lacks quality assurance. Therefore, we designed an enterprise-grade CI/CD pipeline that combines the strengths of GitHub Actions and Vercel.
- CI/CD Collaboration: We clarified the separation of responsibilities: GitHub Actions (CI) acts as the "gatekeeper," ensuring code quality through
lint,typecheck, andtest; Vercel (CD) acts as the "delivery person," responsible for building and delivering both preview deployments (for review) and production deployments (for going live). - Code Implementation: We wrote a
.github/workflows/ci.ymlfile that leverages pnpm caching to efficiently run our quality checks on every commit and PR.
Through this workflow, we achieve the "rapid development" promised by Next.js/Vercel while adding the "safety and reliability" required by traditional DevOps.
More Posts
Chapter 22: Conclusion - Becoming a Next.js Full-Stack Architect
If you've followed along from Chapter 1, you've made a remarkable journey: from an experienced Python backend developer to a full-stack architect who can navigate the modern JavaScript ecosystem.
Chapter 1: Hello, JavaScript (A Python Developer's Perspective)
This is the first and most critical mindset shift you'll experience as a Python backend developer.
Chapter 9: [Deep Dive] Multi-Tenant Architecture Design
Welcome to Chapter Nine. So far, we've built a single-user system: users log in and create their own projects. But almost all successful SAAS (Software as a Service) applications (like Slack, Notion, Figma) are not single-user systems, they are multi-tenant systems.