Continuous integration and continuous deployment ensure every code change is automatically tested and deployed. Here is how to set it up with GitHub Actions.
Step 1: Basic CI Pipeline
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm lint
typecheck:
name: Type Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm tsc --noEmit
test:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm test
build:
name: Build
runs-on: ubuntu-latest
needs: [lint, typecheck, test]
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm build
Step 2: Cache Dependencies
Speed up builds by caching pnpm store:
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
This automatically caches and restores pnpm dependencies.
Step 3: Cache Next.js Build
- uses: actions/cache@v4
with:
path: ${{ github.workspace }}/.next/cache
key: nextjs-${{ hashFiles('pnpm-lock.yaml') }}-${{ hashFiles('**/*.ts', '**/*.tsx') }}
restore-keys: |
nextjs-${{ hashFiles('pnpm-lock.yaml') }}-
Step 4: Add E2E Tests
e2e:
name: E2E Tests
runs-on: ubuntu-latest
needs: [build]
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: npx playwright install --with-deps chromium
- run: pnpm build
- run: pnpm test:e2e --project=chromium
- uses: actions/upload-artifact@v4
if: failure()
with:
name: playwright-report
path: playwright-report/
retention-days: 7
Step 5: Environment Variables
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
NEXT_PUBLIC_SITE_URL: ${{ vars.NEXT_PUBLIC_SITE_URL }}
Set secrets in GitHub repository settings under Settings → Secrets and variables → Actions.
Step 6: Deploy to Vercel
Vercel deploys automatically on push. For custom control:
deploy:
name: Deploy
runs-on: ubuntu-latest
needs: [build, e2e]
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: "--prod"
Step 7: Preview Deployments for PRs
preview:
name: Preview Deploy
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
needs: [lint, typecheck, test]
steps:
- uses: actions/checkout@v4
- uses: amondnet/vercel-action@v25
id: deploy
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
- uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `Preview: ${{ steps.deploy.outputs.preview-url }}`
})
Step 8: Database Migrations
migrate:
name: Run Migrations
runs-on: ubuntu-latest
needs: [build]
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm db:migrate
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
Step 9: Scheduled Health Checks
# .github/workflows/health.yml
name: Health Check
on:
schedule:
- cron: "0 */6 * * *" # Every 6 hours
jobs:
check:
runs-on: ubuntu-latest
steps:
- name: Check site status
run: |
STATUS=$(curl -o /dev/null -s -w "%{http_code}" https://yoursite.com)
if [ "$STATUS" != "200" ]; then
echo "Site returned $STATUS"
exit 1
fi
Step 10: Full Pipeline Summary
PR opened → lint + typecheck + test (parallel)
→ build
→ e2e tests
→ preview deploy + comment
Merge to main → lint + typecheck + test (parallel)
→ build
→ e2e tests
→ run migrations
→ deploy to production
Add Scripts to package.json
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"test": "vitest run",
"test:e2e": "playwright test",
"db:migrate": "drizzle-kit migrate"
}
}
Need DevOps for Your Project?
We set up CI/CD pipelines, automated testing, and deployment workflows for web applications. Contact us to streamline your development process.