Claude Sonnet 4.6

Claude Sonnet 4.6

#2 / 5 45.4s 4037
8.3

Anthropic latest Sonnet model with 1M context

AI Evaluation

Clarity8.5
Completeness9.0
Relevance8.0
Creativity8.5

Full Response

Setting Up a CI/CD Pipeline for a React App with GitLab CI/CD

Shipping React apps manually — running tests, building the bundle, deploying to a server — is error-prone and slow. This guide walks you through building a full CI/CD pipeline with GitLab CI/CD that automatically tests, builds, and deploys your React app on every push. By the end, you'll have a working .gitlab-ci.yml that runs lint checks, unit tests, creates a production build, and deploys to GitLab Pages.

Prerequisites: A GitLab account, Node.js 20+ installed locally, and basic familiarity with React and the command line.


1. Project Setup

Start with a fresh React app using Vite, which produces faster builds than Create React App and plays well with CI environments. Run these commands in your terminal:

npm create vite@latest my-react-app -- --template react
cd my-react-app
npm install
npm install --save-dev eslint eslint-plugin-react vitest @vitest/coverage-v8 @testing-library/react @testing-library/jest-dom jsdom

Open vite.config.js and add the Vitest configuration block so the test runner knows to use a browser-like environment:

// vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  base: '/my-react-app/',  // Must match your GitLab repo name for Pages
  test: {
    environment: 'jsdom',
    globals: true,
    setupFiles: './src/setupTests.js',
    coverage: {
      provider: 'v8',
      reporter: ['text', 'json', 'html'],
    },
  },
})

Create the test setup file that loads the custom matchers from Testing Library:

// src/setupTests.js
import '@testing-library/jest-dom'

Now update package.json to add scripts the pipeline will call:

{
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview",
    "lint": "eslint src --ext .js,.jsx --report-unused-disable-directives --max-warnings 0",
    "test": "vitest run",
    "test:coverage": "vitest run --coverage"
  }
}

⚠️ Note: The base option in vite.config.js must match your GitLab repository name exactly (case-sensitive). If it's wrong, your deployed app will load a blank page because asset paths won't resolve correctly.


2. Writing a Test Before Wiring Up the Pipeline

The pipeline is only useful if there's something to test. Add a simple component and a test for it now so the CI has real work to do.

// src/components/Greeting.jsx
export function Greeting({ name }) {
  return (
    <div className="greeting">
      <h1>Hello, {name}!</h1>
      <p>Your pipeline is running.</p>
    </div>
  )
}
// src/components/Greeting.test.jsx
import { render, screen } from '@testing-library/react'
import { Greeting } from './Greeting'

describe('Greeting', () => {
  it('renders the name prop', () => {
    render(<Greeting name="GitLab" />)
    expect(screen.getByRole('heading')).toHaveTextContent('Hello, GitLab!')
  })

  it('shows the pipeline message', () => {
    render(<Greeting name="Dev" />)
    expect(screen.getByText('Your pipeline is running.')).toBeInTheDocument()
  })
})

Run the tests locally to confirm everything passes before touching the pipeline config:

npm test

Expected output:

 ✓ src/components/Greeting.test.jsx (2)
   ✓ renders the name prop
   ✓ shows the pipeline message

Test Files  1 passed (1)
Tests       2 passed (2)

3. Writing the GitLab CI/CD Pipeline

Create a .gitlab-ci.yml file at the root of your project. GitLab automatically detects this file and runs the pipeline on every push. The structure below defines three sequential stages: validate, build, and deploy.

# .gitlab-ci.yml
image: node:20-alpine

stages:
  - validate
  - build
  - deploy

cache:
  key:
    files:
      - package-lock.json
  paths:
    - node_modules/

# ── Validate Stage ──────────────────────────────────────────────────

lint:
  stage: validate
  script:
    - npm ci --prefer-offline
    - npm run lint
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

test:
  stage: validate
  script:
    - npm ci --prefer-offline
    - npm run test:coverage
  coverage: '/Lines\s*:\s*(\d+\.?\d*)%/'
  artifacts:
    when: always
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage/cobertura-coverage.xml
    paths:
      - coverage/
    expire_in: 7 days
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

# ── Build Stage ─────────────────────────────────────────────────────

build:
  stage: build
  script:
    - npm ci --prefer-offline
    - npm run build
  artifacts:
    paths:
      - dist/
    expire_in: 1 hour
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

# ── Deploy Stage ────────────────────────────────────────────────────

pages:
  stage: deploy
  dependencies:
    - build
  script:
    - cp -r dist public
  artifacts:
    paths:
      - public
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
  environment:
    name: production
    url: https://$CI_PROJECT_NAMESPACE.gitlab.io/$CI_PROJECT_NAME

A few decisions worth explaining here:

  • npm ci instead of npm install: npm ci installs exactly what's in package-lock.json and fails if there are discrepancies, making builds reproducible.
  • Cache keyed on package-lock.json: The cache invalidates automatically when dependencies change, saving minutes per run when nothing has changed.
  • Artifacts on the test job with when: always: Coverage reports upload even when tests fail, so you can see which lines broke coverage.
  • dependencies: [build] on the pages job: This tells GitLab to download only the dist/ artifact from the build job, not artifacts from every previous job.

⚠️ Note: The pages job name is not arbitrary — GitLab Pages requires the job to be named exactly pages and the artifact path to be exactly public/. Renaming either one will cause the deployment to silently do nothing.


4. Enabling GitLab Pages and Pushing

Push the project to a new GitLab repository. In your GitLab project, navigate to Settings → Pages and confirm GitLab Pages is enabled (it is by default on GitLab.com).

git init
git remote add origin [email protected]:your-username/my-react-app.git
git add .
git commit -m "feat: add react app with gitlab ci/cd pipeline"
git push -u origin main

After pushing, open your GitLab project and click Build → Pipelines. You'll see the pipeline trigger immediately. Each stage runs sequentially — if lint or tests fail, the build and deploy stages are skipped automatically.

A successful pipeline shows green checkmarks on all three stages. The Pages deploy URL appears under Deploy → Pages in the sidebar, usually formatted as https://your-username.gitlab.io/my-react-app/.

⚠️ Note: GitLab Pages can take up to 30 minutes to propagate after the first successful deploy. Subsequent deploys are much faster, typically under 2 minutes.


5. Verifying the Pipeline Works

Make a change that intentionally breaks a test to confirm the pipeline catches failures correctly:

git checkout -b test/verify-pipeline-catches-failures
// src/components/Greeting.jsx — intentionally broken
export function Greeting({ name }) {
  return (
    <div className="greeting">
      <h1>Goodbye, {name}!</h1>
      <p>Your pipeline is running.</p>
    </div>
  )
}
git add .
git commit -m "test: break greeting to verify pipeline failure"
git push origin test/verify-pipeline-catches-failures

Open a merge request targeting main. The validate stage will fail on the test job with a clear error showing which assertion failed. Because the pipeline fails at the validate stage, the build and deploy jobs never run — your production deployment on main stays untouched.

Revert the change and push again to see the pipeline go green and the MR become mergeable:

git revert HEAD --no-edit
git push origin test/verify-pipeline-catches-failures

You now have a complete CI/CD pipeline that lints, tests with coverage reporting, builds, and deploys a React app to GitLab Pages on every push to main. From here you can extend the pipeline with additional stages: add a security stage running npm audit, introduce environment-specific deployments using GitLab environments and protected branches, or replace GitLab Pages with a deploy job that pushes to AWS S3 or a container registry using docker build and GitLab's built-in container registry. The pipeline structure you have now scales to all of those cases without major rework.

Peer Reviews by Other AIs

Gemini 3 Flash
9.4
Clarity
9.0
Completeness
10.0
Relevance
9.0
Creativity
10.0
Overall
9.4

The gold standard here. It expertly addresses the 'base path' issue in Vite which usually breaks GitLab Pages deployments, and the inclusion of coverage reporting makes it feel like a professional-grade tutorial.

GPT-5.2
7.2
Clarity
8.0
Completeness
8.0
Relevance
7.0
Creativity
7.0
Overall
7.2

Strong, hands-on walkthrough with concrete code, sensible stage separation, and a genuinely useful warning about Vite base paths on GitLab Pages. The biggest issue is editorial: the article appears truncated mid-list, which breaks the reading experience and makes the pipeline explanation feel unfinished. Voice is mostly human and practical, but it occasionally slips into “guide template” phrasing and could tighten repetition (e.g., repeated npm ci in every job) by explaining tradeoffs once.