Skip to main content
Tooling
11 min read
February 12, 2026

CI/CD for Frontend Teams: From Zero to Production Confidence

How to set up GitHub Actions pipelines that catch bugs before your users do

Segev Sinay

Segev Sinay

Frontend Architect

Share:

The most expensive bug is the one your users find. The second most expensive is the one your QA team finds. The cheapest? The one your CI pipeline catches before the code ever leaves the developer's branch.

I set up CI/CD pipelines for every team I work with. Not because it's fun (it is), but because the ROI is absurd. A pipeline that takes one day to set up catches thousands of bugs over the lifetime of a project. Here's exactly how to build one.

The Minimum Viable Pipeline

Every frontend project needs at least this:

# .github/workflows/ci.yml
name: CI

on:
  pull_request:
    branches: [main]
  push:
    branches: [main]

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: Type check
        run: npx tsc --noEmit

      - name: Lint
        run: npx eslint . --max-warnings 0

      - name: Test
        run: npm test -- --coverage --watchAll=false

      - name: Build
        run: npm run build

Four gates: types, lint, test, build. If any fail, the PR can't merge. This alone prevents ~60% of the bugs I see in codebases without CI.

Gate 1: TypeScript Type Checking

tsc --noEmit catches an entire category of bugs that linters miss. Interface mismatches, incorrect function signatures, missing null checks — all caught at compile time.

Pro tip: Use strict mode. Always.

{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitReturns": true,
    "exactOptionalPropertyTypes": true
  }
}

Yes, it's more work upfront. No, you won't regret it.

Gate 2: Linting With Zero Tolerance

--max-warnings 0 is the key flag. Without it, warnings accumulate until your linter output is so noisy that nobody reads it. With it, every warning must be fixed or explicitly disabled (with a comment explaining why).

// eslint.config.js
export default [
  {
    rules: {
      "no-console": "warn",
      "no-unused-vars": "error",
      "prefer-const": "error",
      "react-hooks/exhaustive-deps": "warn",
      "react-hooks/rules-of-hooks": "error",
    },
  },
];

Gate 3: Tests

You don't need 100% coverage. You need tests for the things that would be embarrassing if they broke.

What to test:

  • User-facing flows (login, checkout, form submission)
  • Business logic (price calculation, permission checks)
  • Edge cases (empty states, error states, boundary values)

What not to test:

  • Implementation details (component internal state)
  • Third-party library behavior
  • Static UI (unless it's critical branding)

The Testing Strategy That Works

Unit tests (Vitest):        Business logic, utilities, hooks
Integration tests (RTL):     Component interactions, form flows
E2E tests (Playwright):      Critical user journeys (2-5 tests)

A startup doesn't need 500 tests. It needs 50 well-chosen ones that cover the paths users actually take.

Gate 4: Build Verification

If it doesn't build, it doesn't ship. Simple as that. The build step also catches:

  • Missing imports
  • Tree-shaking issues
  • Environment variable problems
  • Asset optimization failures

Preview Deployments

This is the single best investment you can make in your deployment workflow. Every PR gets its own live URL that stakeholders can review.

With Vercel: It's automatic. Push a branch, get a preview URL. Done.

With GitHub Actions + custom hosting:

  preview:
    if: github.event_name == 'pull_request'
    needs: quality
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run build

      - name: Deploy Preview
        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: ' + process.env.PREVIEW_URL
            });

Now your designer, PM, or CEO can review changes without pulling code or understanding git. This alone cut code review time in half on teams I've worked with.

Advanced: Bundle Size Monitoring

Track your JavaScript bundle size over time. Alert when it grows unexpectedly.

  bundle-analysis:
    needs: quality
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run build

      - name: Analyze bundle
        run: npx next-bundle-analyzer || true

      - name: Check size limits
        uses: andresz1/size-limit-action@v1
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}

Advanced: Automated Dependency Updates

# .github/workflows/deps.yml
name: Update Dependencies

on:
  schedule:
    - cron: "0 9 * * 1"  # Every Monday at 9am

jobs:
  update:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - run: npm ci
      - run: npx npm-check-updates -u --target minor
      - run: npm install
      - run: npm test -- --watchAll=false
      - run: npm run build

      - name: Create PR
        uses: peter-evans/create-pull-request@v6
        with:
          title: "chore: update dependencies"
          body: "Automated dependency update"
          branch: deps/weekly-update

Dependencies get updated weekly. If tests pass, the PR is ready to merge. If they fail, you know something needs attention — before it becomes a security vulnerability.

The Pipeline I Set Up For Every Client

  1. PR opened: Type check → Lint → Test → Build → Preview deploy → Bundle size comment
  2. PR merged to main: Build → Deploy to production → Smoke test → Slack notification
  3. Weekly: Dependency update PR → Automated tests → Security audit

Total setup time: one day. Bugs caught per month: dozens. Developer confidence: immeasurable.

The goal isn't a perfect pipeline. The goal is a pipeline that catches the mistakes humans make when they're tired, rushing, or context-switching between three features. Computers don't get tired. Let them do the boring work.

CI/CD
GitHub Actions
testing
deployment
automation
DevOps
quality gates

Related Articles

Get started

Ready to Level Up Your Product?

I take on a handful of companies at a time. Reach out to discuss your challenges and see if there's a fit.

Send a Message