Documentation
CI/CD
CI Workflows

CI Workflows

Ready-to-use workflow configurations for your React projects.

Complete CI Workflow

# .github/workflows/ci.yml
name: CI
 
on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]
 
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true
 
jobs:
  lint:
    name: Lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
 
      - uses: pnpm/action-setup@v3
        with:
          version: 9
 
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'pnpm'
 
      - run: pnpm install --frozen-lockfile
 
      - name: ESLint
        run: pnpm lint
 
      - name: Prettier
        run: pnpm prettier --check .
 
  typecheck:
    name: Type Check
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
 
      - uses: pnpm/action-setup@v3
        with:
          version: 9
 
      - 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@v3
        with:
          version: 9
 
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'pnpm'
 
      - run: pnpm install --frozen-lockfile
 
      - name: Run tests
        run: pnpm test -- --coverage
 
      - name: Upload coverage
        uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          files: ./coverage/lcov.info
 
  build:
    name: Build
    needs: [lint, typecheck, test]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
 
      - uses: pnpm/action-setup@v3
        with:
          version: 9
 
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'pnpm'
 
      - run: pnpm install --frozen-lockfile
 
      - name: Build
        run: pnpm build
        env:
          NEXT_PUBLIC_API_URL: ${{ vars.NEXT_PUBLIC_API_URL }}
 
      - name: Upload build
        uses: actions/upload-artifact@v4
        with:
          name: build
          path: .next/
          retention-days: 7

E2E Testing with Playwright

# .github/workflows/e2e.yml
name: E2E Tests
 
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
 
jobs:
  e2e:
    name: Playwright Tests
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
 
      - uses: pnpm/action-setup@v3
        with:
          version: 9
 
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'pnpm'
 
      - run: pnpm install --frozen-lockfile
 
      - name: Install Playwright browsers
        run: pnpm exec playwright install --with-deps chromium
 
      - name: Build
        run: pnpm build
 
      - name: Run E2E tests
        run: pnpm exec playwright test
        env:
          CI: true
 
      - name: Upload test results
        uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 7

Dependency Updates (Dependabot)

# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: 'npm'
    directory: '/'
    schedule:
      interval: 'weekly'
      day: 'monday'
    open-pull-requests-limit: 10
    groups:
      development:
        patterns:
          - '@types/*'
          - 'eslint*'
          - 'prettier'
          - 'typescript'
      react:
        patterns:
          - 'react*'
          - 'next*'
      testing:
        patterns:
          - 'jest'
          - '@testing-library/*'
          - 'playwright'

Auto-merge Dependabot PRs

# .github/workflows/dependabot-auto-merge.yml
name: Dependabot Auto-merge
 
on: pull_request
 
permissions:
  contents: write
  pull-requests: write
 
jobs:
  auto-merge:
    runs-on: ubuntu-latest
    if: github.actor == 'dependabot[bot]'
    steps:
      - name: Fetch Dependabot metadata
        id: metadata
        uses: dependabot/fetch-metadata@v2
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
 
      - name: Auto-merge minor and patch updates
        if: steps.metadata.outputs.update-type == 'version-update:semver-minor' || steps.metadata.outputs.update-type == 'version-update:semver-patch'
        run: gh pr merge --auto --squash "$PR_URL"
        env:
          PR_URL: ${{ github.event.pull_request.html_url }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Release Workflow

# .github/workflows/release.yml
name: Release
 
on:
  push:
    tags:
      - 'v*'
 
jobs:
  release:
    name: Create Release
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
 
      - name: Generate changelog
        id: changelog
        uses: orhun/git-cliff-action@v3
        with:
          args: --latest --strip header
 
      - name: Create Release
        uses: softprops/action-gh-release@v2
        with:
          body: ${{ steps.changelog.outputs.content }}
          draft: false
          prerelease: ${{ contains(github.ref, '-') }}

Preview Deployments

# .github/workflows/preview.yml
name: Preview Deployment
 
on:
  pull_request:
    types: [opened, synchronize, reopened]
 
jobs:
  preview:
    name: Deploy Preview
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - uses: actions/checkout@v4
 
      - name: Deploy to Vercel
        id: deploy
        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 }}
 
      - name: Comment PR
        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 Deployment
 
              | Name | Link |
              |------|------|
              | Preview | [${{ steps.deploy.outputs.preview-url }}](${{ steps.deploy.outputs.preview-url }}) |
 
              Built from commit: \`${{ github.sha.substring(0, 7) }}\``
            })

Lighthouse CI

# .github/workflows/lighthouse.yml
name: Lighthouse CI
 
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
 
jobs:
  lighthouse:
    name: Lighthouse
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
 
      - uses: pnpm/action-setup@v3
        with:
          version: 9
 
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'pnpm'
 
      - run: pnpm install --frozen-lockfile
      - run: pnpm build
 
      - name: Run Lighthouse CI
        uses: treosh/lighthouse-ci-action@v11
        with:
          uploadArtifacts: true
          temporaryPublicStorage: true
          configPath: './lighthouserc.json'
// lighthouserc.json
{
  "ci": {
    "collect": {
      "startServerCommand": "pnpm start",
      "url": ["http://localhost:3000"],
      "numberOfRuns": 3
    },
    "assert": {
      "assertions": {
        "categories:performance": ["warn", { "minScore": 0.9 }],
        "categories:accessibility": ["error", { "minScore": 0.9 }],
        "categories:best-practices": ["warn", { "minScore": 0.9 }],
        "categories:seo": ["warn", { "minScore": 0.9 }]
      }
    }
  }
}

Security Scanning

# .github/workflows/security.yml
name: Security
 
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 0 * * 0'  # Weekly on Sunday
 
jobs:
  audit:
    name: npm audit
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm audit --audit-level=high
 
  codeql:
    name: CodeQL Analysis
    runs-on: ubuntu-latest
    permissions:
      security-events: write
    steps:
      - uses: actions/checkout@v4
 
      - name: Initialize CodeQL
        uses: github/codeql-action/init@v3
        with:
          languages: javascript-typescript
 
      - name: Perform CodeQL Analysis
        uses: github/codeql-action/analyze@v3

Branch Protection Settings

Recommended branch protection rules for main:

  • Require pull request reviews
  • Require status checks to pass:
    • lint
    • typecheck
    • test
    • build
  • Require branches to be up to date
  • Require conversation resolution
  • Do not allow bypassing

Best Practices

  1. Cancel redundant runs - Use concurrency to cancel previous runs
  2. Parallel jobs - Run independent checks concurrently
  3. Fail fast - Stop other jobs if one fails
  4. Cache everything - Dependencies, build artifacts, test results
  5. Use artifacts - Share build outputs between jobs
  6. Automate releases - Use semantic versioning and changelogs