What you'll learn
- Tell CI from CD and say what each one automates
- Say what each pipeline step catches, from npm ci through the production build
- Read a GitHub Actions workflow file and know what every line is there for
- Make CI mandatory with branch protection so a failing check blocks the merge
- Gate the deploy step so it runs on merges to main and not on every PR
on this page
CI/CD is one of those acronyms that gets thrown around constantly. “We have CI/CD.” “The CI is failing.” “We need to set up a pipeline.”
But what does it actually do? And why should you care?
CI and CD are two separate things
CI: Continuous Integration
Every time code gets pushed or a PR is opened, an automated system:
- Pulls the latest code
- Installs dependencies
- Runs type checks, linting, and tests
- Builds the app
If any step fails, the PR is blocked from merging. This ensures the codebase always stays in a working state.
CD: Continuous Delivery / Deployment
After CI passes (and optionally after a merge to main), the system automatically deploys the new version to staging or production.
Together, they mean: merge to main, and it ships automatically.
What belongs in a pipeline?
Use the builder below to understand which steps matter and why:
* Critical steps. Disabling them drops confidence regardless of score. 6/8 steps enabled.
Here’s what each step does and why it earns its place:
1. Install dependencies
- run: npm ci
npm ci (clean install) is stricter than npm install. It:
- Installs exact versions from
package-lock.json - Fails if the lockfile is out of sync with
package.json - Never modifies
package-lock.json
Always use npm ci in CI, never npm install.
2. Type check
- run: npx tsc --noEmit
Catch TypeScript errors that might not have been caught locally. This is cheap and fast. There’s no excuse to skip it.
3. Lint
- run: npx eslint .
Enforces code style, catches potential bugs (unused variables, missing await, etc.). Keeps the codebase consistent.
4. Unit tests
- run: npm test
Your business logic should be tested. CI is what makes tests actually matter, without it, tests are optional suggestions.
5. Build
- run: npm run build
Make sure the production build compiles without errors. Something might pass type checking but still fail to build.
6. Deploy
On a successful merge to main, trigger the deployment to production.
A basic GitHub Actions pipeline
GitHub Actions is the most common CI/CD tool for projects hosted on GitHub. Configuration lives in .github/workflows/.
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
ci:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "24"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Type check
run: npx tsc --noEmit
- name: Lint
run: npx eslint .
- name: Run tests
run: npm test
- name: Build
run: npm run build
Commit this file, push it, and open a PR. GitHub automatically runs the workflow and shows a green/red check on the PR.
Branch protection: making CI mandatory
CI is useless if people can bypass it. In GitHub:
- Go to Settings → Branches → Add rule
- Set Branch name pattern:
main - Check Require status checks to pass before merging
- Select your CI job (
ci) as required - Check Require branches to be up to date
Now no one can merge a PR that fails CI. Your main branch is protected.
Adding auto-deploy
For a static site on Vercel, deployment is already automatic. Vercel watches your repo. But for more control, you can add a deploy step to your workflow:
- name: Deploy to production
if: github.ref == 'refs/heads/main'
run: npx vercel --prod --token ${{ secrets.VERCEL_TOKEN }}
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
The if: github.ref == 'refs/heads/main' condition ensures this only runs on merges to main, not on every PR.
Why CI/CD matters beyond automation
CI/CD isn’t just about speed. It’s about confidence.
Without it:
- Deployments are manual, scary events
- “We can’t deploy on Fridays” becomes a rule
- One bad commit can take down production for hours
- Nobody wants to merge code late in the day
With it:
- Merging is routine and low-risk
- Bad code is caught before it reaches production
- Deployment is boring and fast
- The feedback loop from “write code” to “it’s in production” is minutes, not days
That shift, from deployment as a scary event to deployment as a normal, automatic thing, is what CI/CD is really about.
signal check
Can you answer these without scrolling back up?
-
What is the actual difference between CI and CD?
CI runs checks on every push or pull request: install, type check, lint, test, build, and a failure blocks the merge. CD picks up after CI passes and deploys the new version automatically, so merging to main means it ships.
-
Why does the pipeline use npm ci instead of npm install?
npm ci is stricter: it installs exact versions from package-lock.json, fails if the lockfile is out of sync with package.json, and never modifies the lockfile. Always npm ci in CI, never npm install.
-
Your team has CI but people merge red PRs anyway. What is missing?
Branch protection. In the repo settings you require status checks to pass before merging and select the CI job as required. After that, nobody can merge a PR that fails CI.
progress is stored in this browser only