Skip to content
By Yash KapureFrontend12 min readJune 27, 2026

How to Set Up CI/CD for a React App

Most React developers can build an app locally but get stuck explaining how code safely reaches production. Here is the exact pipeline I ship in real projects, end to end.

Article focus

Push → Production

One automated path, every gate passed before users see it

Key takeaways

  • CI runs your quality gates on every push; CD takes the code that passed and ships it. Both exist so you stop relying on "it works on my machine".
  • A real React pipeline is one line: push → GitHub → install → lint → typecheck → test → build → preview → review → production → monitor/rollback.
  • What matters is not the tools you name. It is understanding why each gate exists and what breaks when it is missing.
  • CI/CD is not "deployment". It is the confidence to answer one question on every push: is this safe to ship?

Why shipping to production trips up good React developers

You can build a great app locally and still get stuck explaining how that code safely reaches real users. That gap is exactly what CI/CD fills.

Here is a pattern I see constantly. A developer can build a polished React app: clean components, sensible state, nice animations. Then someone asks, "Okay, how does this actually get to production?" and things go quiet.

It is not a knowledge problem. It is that most of us learn React by running npm run dev and never think hard about the path between our laptop and a live URL. That path is CI/CD - and it also happens to be one of the most common system-design questions in frontend interviews, so it is worth getting right twice over.

The good news: the pipeline for a React app is genuinely simple once you see it as a single straight line. This post walks that line end to end, the way I actually wire it up in a real project - with interactive demos so you can run each stage yourself.

CI and CD, explained without the buzzwords

CI = automatically check every change. CD = automatically ship the changes that passed. One protects quality, the other removes manual deploys.

Continuous Integration (CI) means every time you push code, a machine runs your checks for you: install dependencies, lint, type-check, run tests, build. If anything fails, the change is blocked before it can merge. You stop "integrating" once a week in a painful big-bang merge and instead integrate constantly, in small safe steps.

Continuous Delivery / Deployment (CD) is what happens after the checks pass. Continuous Delivery means the build is always ready to ship and a human clicks the button. Continuous Deployment means there is no button: merge to main and it goes live automatically. Most React teams start with Delivery and graduate to Deployment once they trust their tests.

Why does this matter for a React app specifically? Frontend bugs are loud and public. A broken build, a type error that slipped through, a 4 MB bundle, a blank white screen on mobile. CI catches these before users do. CD makes sure the fix ships in minutes, not after someone is free to run a manual deploy.

The mental model: one straight line from push to production

Every modern React pipeline is the same flow. Memorize this line and you can reason about any setup.

Do not memorize tools. Memorize the flow. Tools change (Vercel, Netlify, GitHub Actions, GitLab CI), but the stages are always the same:

Read it left to right. The first half (up to build) is CI: prove the change is good. The second half is CD: get the good change in front of people, first on a private preview, then in production. Monitoring and rollback close the loop so a bad release is a 30-second fix, not an outage.

text

Developer Push
      ↓
   GitHub  (feature branch → Pull Request)
      ↓
──────── CI runs on every push ────────
 Install deps  →  Lint  →  Type check  →  Test  →  Build
────────────────────────────────────────
      ↓
 Preview Deployment  (unique URL per PR)
      ↓
   Review  (code review + design/QA on the preview)
      ↓
   Merge to main
      ↓
 Production Deployment  (only after CI passes + approval)
      ↓
 Monitoring  →  Rollback / Feature flags

Run the pipeline yourself

Hit "Push code" and watch a change move through every stage. Then inject a failing test or build and see the merge get blocked before anything reaches production.

Inject failure
Install deps
Lint
Type check
Test
Build
Preview deploy
Production
CI - prove it is good CD - ship it and watch it

Step 1: Push code to GitHub the right way

Work on a feature branch, open a pull request, and protect main so nothing reaches it without passing CI and review.

It starts before any automation. You never commit straight to main. You branch (feat/checkout-button), push, and open a pull request. The PR is where CI runs and where humans review.

Then you turn on branch protection for main: require the CI checks to pass, require at least one approval, and block force-pushes. This single setting is what turns "we have tests" into "you cannot merge code that breaks the tests". Without it, CI is just a suggestion.

In an interview, mentioning branch protection and required status checks signals you have actually worked on a team, not just solo side projects. It is a small detail that carries a lot of weight.

Try it: branch protection

Toggle protection and the CI status, then try to merge. This is the setting that makes CI mandatory instead of optional.

#42 Add checkout button feat/checkout-button → main
CI / verify - 1 failing check

Branch protection requires status checks to pass. This is what turns "we have tests" into "you cannot merge broken code".

Step 2: The GitHub Actions workflow

A single workflow file runs on every push and pull request: checkout, set up Node 20, install, lint, type-check, test, build.

This is the heart of CI. Drop one file at .github/workflows/ci.yml and GitHub runs it automatically. Here is a production-quality workflow for a Vite or Next.js React app:

A few things worth calling out. npm ci (not npm install) installs exactly what is in your lockfile, so CI is reproducible. The cache: npm option in setup-node caches dependencies between runs, which usually cuts a couple of minutes off each build. And every step runs in order, failing fast: if lint fails, you never waste time on the build.

yaml

name: CI

on:
  push:
    branches: [main]
  pull_request:

jobs:
  verify:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Lint
        run: npm run lint

      - name: Type check
        run: npm run typecheck

      - name: Test
        run: npm test -- --run

      - name: Build
        run: npm run build

Watch the workflow run

A simulated GitHub Actions run for the YAML above - each step streams in order, fail-fast, just like the real thing.

CI · verify
Set up jobRunner ubuntu-latest
Checkoutactions/checkout@v4
Set up Node.jsnode-version: 20 (cache: npm)
Install dependenciesnpm ci
Lintnpm run lint
Type checknpm run typecheck
Testnpm test -- --run
Buildnpm run build

Step 3: The quality gates that actually matter

Each step in the workflow is a gate. The art is knowing what each one catches and why skipping it hurts.

A pipeline is only as good as the checks inside it. Here is what each gate does and the bug it stops:

Start with the first four; they are cheap and catch the majority of issues. Bundle size, audit, accessibility, and Lighthouse are the ones that separate a hobby setup from a professional one, and they are exactly what a strong interview answer reaches for.

Try it: the quality gates

Toggle each gate to see what it catches, and drag the bundle-size slider past budget to watch the gate fail the PR.

Bundle size checkWithin budget - CI passes
180 KB / 250 KB

Drag the slider. A careless import pushes the bundle past budget and the gate fails the PR - before users download it.

Step 4: Preview deployments

Every pull request gets its own live URL so reviewers, designers, QA, and product can see the real thing before it merges.

This is the feature that makes frontend CI/CD feel magical, and Vercel and Netlify give it to you almost for free. When you open a PR, the platform builds your branch and posts a unique preview URL right in the PR.

The value is huge and underrated. A designer can check spacing on the real site instead of a screenshot. QA can click through the actual flow. A product manager can sign off on the feature without pulling the branch and running it locally. Everyone reviews the same deployed artifact, not their own mental model of it.

In an interview, this is a great point to make: "I would set up preview deployments so review happens on a real URL, not on a diff." It shows you think about the whole team, not just the code.

Try it: preview deployment

Open the PR and watch the preview build, then switch between desktop and mobile - exactly what your reviewers get.

vercelbot commented
Status- Waiting
Previewassigning URL…

Designers, QA, and product review this real URL - not a screenshot, not a local build.

Step 5: Production deployment

Production deploys only after CI passes, the PR is approved, and the code is merged to main. Never from a laptop.

Once the PR is green and approved, you merge to main. That merge is the trigger. With Vercel or Netlify connected to the repo, a push to main automatically builds and deploys to production. With GitHub Actions, you add a deploy job that runs only on main and only after the verify job succeeds.

The non-negotiable rule: production deployment is a consequence of merging good code, never a manual command someone runs from their machine. Deploying from a laptop means deploying uncommitted changes, the wrong branch, or a build no one else can reproduce. The pipeline is the only thing allowed to ship.

Optionally, you gate production behind a manual approval (Continuous Delivery) for high-risk apps, or let it flow automatically (Continuous Deployment) once your test suite earns that trust.

Try it: how production deploys

Promote a change through the pipeline - or try deploying from a laptop and see why that path is blocked.

CI passed on main
PR approved & merged
Deployed to production

Step 6: Monitoring and rollback

Shipping is the middle of the story, not the end. You need eyes on production and a fast way to undo a bad release.

A green pipeline tells you the code built and passed tests. It does not tell you the feature works for a real user on a slow phone in production. That is what monitoring is for, and it is the part most developers forget to mention.

Wire up error monitoring (Sentry or similar) so a runtime exception pages you instead of sitting silently in a user’s console. Add performance monitoring to catch a regression in load time. Keep logs you can actually search.

Then have a rollback plan. On Vercel and Netlify, every deploy is immutable, so rolling back is one click to a previous build, seconds, not a panicked revert-and-redeploy. Feature flags take it further: ship code dark, turn the feature on for 5% of users, and kill it instantly if metrics dip without any redeploy at all.

  • Error monitoring – Sentry, so exceptions reach you, not just the user’s console.
  • Logs – searchable, structured, retained long enough to debug yesterday’s incident.
  • Performance monitoring – track real-user Core Web Vitals, not just lab scores.
  • Rollback – immutable deploys mean reverting is one click to a known-good build.
  • Feature flags – decouple "deployed" from "released"; flip a feature off without shipping new code.

Try it: monitoring and rollback

Ship a bad release, watch the error rate spike, then recover in one click - by rolling back or flipping a feature flag.

Error rate0.1%
Live deployv24

Latest release (immutable build)

All healthy. Monitoring is watching so a bad release is a 30-second fix, not an outage.

Interview-ready answer

When an interviewer asks "How would you set up CI/CD for a React application?", here is a tight, confident answer you can say almost verbatim.

"I’d start with branch protection on main so nothing merges without passing checks and a review. Every push and pull request triggers a GitHub Actions workflow on Node 20 that installs with npm ci, then runs lint, type-check, tests, and a production build, failing fast at the first broken step. On top of that I’d add a few high-value gates: bundle-size limits, a dependency audit, accessibility checks, and Lighthouse CI for performance.

For deployment I’d connect Vercel or Netlify so every PR gets a preview URL, which lets designers, QA, and product review the real thing before it merges. Production only deploys after CI is green and the PR is approved and merged to main, never from someone’s laptop. After release I’d watch it with Sentry for errors and real-user performance monitoring, and keep rollback to one click using immutable deploys, with feature flags for risky changes. The whole point is that every push can confidently answer one question: is this safe to ship?"

Common mistakes developers make

Most broken pipelines fail in the same predictable ways. Naming these in an interview shows real experience.

I have seen every one of these in real codebases. Each is a small shortcut that quietly removes the safety the pipeline was supposed to give:

The anti-pattern deck

Eight ways teams quietly break their pipeline. Flip each card to turn the mistake into the fix.

Tap a card to flip the mistake into the fix.

How I’d improve this for a production-grade app

The setup above is solid. Here is what I’d layer on as the app and team grow.

You do not need all of this on day one, and saying so in an interview shows judgment. But here is the path from "good" to "production-grade", roughly in the order I would add them:

Level up your pipeline

Click through the maturity track to see what to add - and in what order - as your app and team grow.

Baseline

You already have this

25%
  • Lint, type-check, test & build on every PR
  • Branch protection with required status checks
You do not need all of this on day one - and saying so in an interview shows judgment. Climb the ladder as the app and team grow.

The takeaway

A good CI/CD pipeline is not about automation for its own sake. It is about confidence.

Strip away the tools and the YAML, and CI/CD is one idea: every change earns its way to production by passing the same checks, every time, automatically. No heroics, no "works on my machine", no Friday-afternoon deploy anxiety.

That is also why it is such a good interview question. It is not really testing whether you know the name of a GitHub Action. It is testing whether you understand how software safely reaches users.

So whether you are wiring this up for a real project or explaining it across a table, anchor everything to one question. Every push should be able to answer it: is this safe to ship?

Need help implementing this?

I build these systems for a living - let's work on yours.

Frontend Development

Production UIs in React, Next.js, or Vue- third-party and payment integrations included. Built for real traffic and maintained in production.

ReactNext.jsTypeScriptTailwind CSSshadcn/ui+5
Discuss this service

Full Stack Development

End-to-end apps: MERN, React+Node, or MEVN. Auth, role-based access, and real-time where it matters. Designed to scale without rewrites.

MERNMEVNNode.jsExpressReact+5
Discuss this service

Frontend Architecture & System Design

Structure so teams can ship. Clear boundaries, state strategy, and contracts. New features land cleanly; refactors stay low-risk.

TypeScriptComponent designState managementAPI contracts+4
Discuss this service
$ git checkout --your-opinion

Your turn

  • >Did this help you ship something?
  • >Which part clicked the most for you?
  • >Applying this at work? Share your experience.

Discussion

Leave a comment

Thoughts, questions, corrections - all welcome.

Sign in with GitHub to comment - free & takes 10 seconds

Recommended blogs

Continue reading

View all blogs
Person typing on a laptop at a focused workspace
SEO
11 min readMarch 8, 2026

SEO, AEO, and GEO for a Modern Developer Portfolio

How I structure a portfolio so Google, featured snippets, and AI crawlers can all quote me without me sounding like a keyword vending machine.

Photo by Mikhail Nilov on Pexels

Read article
Abstract visualization of asynchronous JavaScript - glowing connected nodes representing Promise chains and async flow
Frontend
22 min readMay 1, 2026

Mastering Async JavaScript: Promises, Async/Await, and the Microtask Queue

From callback hell to Promise chains to async/await, this guide covers everything about asynchronous JavaScript including error handling, Promise combinators, AbortController, and real-world patterns.

Pexels - Free Stock Photo

Read article

Subscribe for new posts, or read how referrals and sponsored placements are handled on this site.