GPT-5.2

GPT-5.2

#1 / 5 58.4s 4609
8.4

OpenAI의 최신 플래그십 모델

AI 평가

명확성8.5
완성도9.0
관련성7.5
창의성8.5

전체 응답

This guide builds a GitLab CI/CD pipeline for a React app that runs tests, produces a production build, and deploys it automatically to GitLab Pages. You’ll solve the common “works on my machine” problem by making builds reproducible and deployments automatic on every push to main. Prerequisites: Node.js 20+, a GitLab account with a project you can push to, and basic familiarity with Git and React.

Setup: Create the React app and prepare the repo

We’ll create a React app with Vite because it’s fast and has a clean build output (a dist folder) that maps nicely to CI artifacts and GitLab Pages. We’ll also add a simple test setup so the pipeline can fail early when code breaks. The end result is a repo where local commands match what CI runs.

1) Create the project

Run the following commands to scaffold a React + TypeScript app, install dependencies, and verify it runs locally. This mirrors what CI will do later, so you want it clean and repeatable. If your local build passes, CI should pass too.

mkdir react-gitlab-cicd
cd react-gitlab-cicd

npm create vite@latest . -- --template react-ts
npm install

npm run dev -- --host 0.0.0.0 --port 5173

Expected result: your terminal prints a local URL like http://localhost:5173, and the default Vite React page loads in your browser.

⚠️ Note: If you’re on Node 18, Vite and tooling may still work, but your CI image should match local. This tutorial uses Node 20 to avoid “works locally, fails in CI” issues caused by version mismatches.

2) Add tests (Vitest + React Testing Library)

CI needs a fast, deterministic test command. Vitest integrates well with Vite, and React Testing Library gives you user-focused tests that catch regressions without being brittle. We’ll add a single test so you can see the pipeline fail if it breaks.

npm install -D vitest jsdom @testing-library/react @testing-library/jest-dom @types/testing-library__jest-dom

Now create a Vitest config so tests run in a browser-like environment (jsdom). This is required for React components that touch the DOM.

import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react";

export default defineConfig({
  plugins: [react()],
  test: {
    environment: "jsdom",
    setupFiles: ["./src/test/setup.ts"],
    globals: true,
  },
});

Create the test setup file to enable helpful DOM matchers like toBeInTheDocument. This keeps your test code clean and readable.

import "@testing-library/jest-dom";

Update package.json to include a stable test command for CI. CI should call the same scripts developers run locally.

{
  "name": "react-gitlab-cicd",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview --host 0.0.0.0 --port 4173",
    "test": "vitest run",
    "test:watch": "vitest"
  },
  "dependencies": {
    "react": "^18.3.1",
    "react-dom": "^18.3.1"
  },
  "devDependencies": {
    "@testing-library/jest-dom": "^6.6.3",
    "@testing-library/react": "^16.0.1",
    "@types/testing-library__jest-dom": "^6.0.0",
    "@vitejs/plugin-react": "^4.3.1",
    "jsdom": "^24.1.3",
    "typescript": "^5.5.4",
    "vite": "^5.4.2",
    "vitest": "^2.0.5"
  }
}

Create a tiny component and a test for it. This gives CI something real to validate and makes failures obvious.

import React from "react";

export function StatusBadge({ status }: { status: "ok" | "error" }) {
  const label = status === "ok" ? "All systems go" : "Something failed";
  const color = status === "ok" ? "#0ea5e9" : "#ef4444";

  return (
    <div
      role="status"
      aria-label="build-status"
      style={{
        display: "inline-block",
        padding: "8px 12px",
        borderRadius: 999,
        background: color,
        color: "white",
        fontFamily: "system-ui, sans-serif",
      }}
    >
      {label}
    </div>
  );
}
import React from "react";
import { render, screen } from "@testing-library/react";
import { describe, it, expect } from "vitest";
import { StatusBadge } from "./StatusBadge";

describe("StatusBadge", () => {
  it("renders ok status text", () => {
    render(<StatusBadge status="ok" />);
    expect(screen.getByRole("status", { name: "build-status" })).toHaveTextContent(
      "All systems go"
    );
  });
});

Wire the component into the app so you can see it locally. This also confirms the build output is what you expect to deploy.

import React from "react";
import ReactDOM from "react-dom/client";
import { StatusBadge } from "./StatusBadge";
import "./index.css";

function App() {
  return (
    <main style={{ padding: 24 }}>
      <h2 style={{ fontFamily: "system-ui, sans-serif" }}>React + GitLab CI/CD</h2>
      <p style={{ fontFamily: "system-ui, sans-serif" }}>
        This badge is tested in CI and deployed via GitLab Pages.
      </p>
      <StatusBadge status="ok" />
    </main>
  );
}

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

Run tests locally to confirm everything is green before adding CI. CI should be boring: it runs the same commands and gets the same results.

npm test
npm run build

Expected result: tests pass, and vite build creates a dist/ folder.

⚠️ Note: Don’t commit dist/. GitLab Pages will serve build artifacts produced by CI, not files committed to the repo.

Implementation: Add GitLab CI jobs (test, build, deploy)

GitLab CI/CD is driven by a single file: .gitlab-ci.yml. We’ll create three stages: test (fast feedback), build (produces deployable assets), and deploy (publishes to GitLab Pages). Each stage will pass artifacts forward so you don’t rebuild unnecessarily.

1) Create the pipeline config

This config caches npm downloads for speed, runs tests on every push and merge request, builds only on main, and deploys to Pages from the build output. GitLab Pages expects a folder named public as the artifact for the pages job.

image: node:20-bullseye

stages:
  - test
  - build
  - deploy

cache:
  key:
    files:
      - package-lock.json
  paths:
    - .npm/
  policy: pull-push

variables:
  NPM_CONFIG_CACHE: "$CI_PROJECT_DIR/.npm"
  NODE_ENV: "production"

test:
  stage: test
  script:
    - npm ci
    - npm test
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH

build:
  stage: build
  script:
    - npm ci
    - npm run build
  artifacts:
    expire_in: 7 days
    paths:
      - dist/
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

pages:
  stage: deploy
  script:
    - rm -rf public
    - mkdir -p public
    - cp -r dist/* public/
  artifacts:
    paths:
      - public
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

Expected result: when you push, GitLab shows a pipeline with a test job, then build, then pages (on main). If tests fail, the pipeline stops before building or deploying.

⚠️ Note: If your project uses pnpm or yarn, don’t keep npm ci in CI. Mixing package managers is a common cause of lockfile and cache weirdness.

2) Push to GitLab

CI only runs once GitLab has the repo. Commit your changes, push to a GitLab project, and watch the pipeline run. This is also where you confirm your runner can install dependencies and execute tests.

git init
git add .
git commit -m "Add React app with tests and GitLab CI pipeline"

git branch -M main
git remote add origin https://gitlab.com/YOUR_NAMESPACE/YOUR_PROJECT.git
git push -u origin main

Expected result: in GitLab, go to CI/CD → Pipelines and see the pipeline succeed. The pages job should upload a public artifact.

Implementation: Configure GitLab Pages and fix base path issues

GitLab Pages serves your site under a URL that often includes your project name, like https://namespace.gitlab.io/project. Single-page apps and Vite builds need the correct base path so assets load from the right place. Without this, you’ll see a blank page or 404s for JS/CSS files.

1) Set the Vite base URL for GitLab Pages

Vite uses base to prefix asset URLs. For GitLab Pages, the safe default is to set base to the repository name when building in CI, but keep “/” locally. We’ll do this by reading an environment variable.

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

const repoBase = process.env.VITE_BASE_PATH ?? "/";

export default defineConfig({
  plugins: [react()],
  base: repoBase,
});

Now update the CI build job to set VITE_BASE_PATH automatically. GitLab provides CI_PROJECT_NAME, which matches the repo name used in the Pages URL.

image: node:20-bullseye

stages:
  - test
  - build
  - deploy

cache:
  key:
    files:
      - package-lock.json
  paths:
    - .npm/
  policy: pull-push

variables:
  NPM_CONFIG_CACHE: "$CI_PROJECT_DIR/.npm"
  NODE_ENV: "production"

test:
  stage: test
  script:
    - npm ci
    - npm test
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH

build:
  stage: build
  script:
    - npm ci
    - VITE_BASE_PATH="/$CI_PROJECT_NAME/" npm run build
  artifacts:
    expire_in: 7 days
    paths:
      - dist/
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

pages:
  stage: deploy
  script:
    - rm -rf public
    - mkdir -p public
    - cp -r dist/* public/
  artifacts:
    paths:
      - public
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

Expected result: after the next successful pipeline on main, your deployed site loads with correct CSS/JS, not a blank page.

⚠️ Note: If your GitLab Pages URL is at the root (user/organization Pages), the base path should be “/”. Project Pages usually need “/project-name/”. Check Deploy → Pages in GitLab for the exact URL.

2) Enable Pages in GitLab

GitLab Pages becomes available when the pages job uploads the public artifact. You then access the URL from the project settings. This is a deployment you can reproduce on every push to main.

  • Go to your GitLab project → Deploy → Pages.
  • Find the Pages URL GitLab provides.
  • Open it and confirm the React app renders.

Expected result: you see the “React + GitLab CI/CD” heading and the status badge on the Pages URL.

Testing/Verification: Prove CI and deployment behave correctly

You want to verify three things: CI fails on broken code, CI produces a build artifact, and Pages updates after merges to main. The checks below are quick and catch the most common pipeline mistakes.

  1. Verify tests gate the pipeline: edit the test to expect the wrong text, commit, and push to a branch or MR.
    git checkout -b break-tests
    # edit src/StatusBadge.test.tsx to expect "Nope"
    git add src/StatusBadge.test.tsx
    git commit -m "Break test on purpose"
    git push -u origin break-tests

    Expected result: the test job fails and the pipeline stops before build/deploy.

  2. Verify build artifacts exist: on a successful main pipeline, open the build job and confirm it uploads dist/ as an artifact.

    Expected result: GitLab shows an artifact browser containing your built assets.

  3. Verify Pages updates: merge a small UI change to main (like changing the heading), then wait for the pipeline to finish.
    git checkout main
    git pull
    
    # make a small change in src/main.tsx
    git add src/main.tsx
    git commit -m "Change heading text"
    git push

    Expected result: the Pages URL shows the updated heading after the pages job completes.

⚠️ Note: If Pages shows an old version, check your browser cache first, then confirm the latest pipeline ran on main. Also confirm the pages job artifact contains the new files (GitLab serves what’s in public).

Now you have a React app with a GitLab CI/CD pipeline that runs tests, builds on main, and deploys to GitLab Pages with the correct base path. Next steps: add ESLint as a separate CI job, publish preview deployments for merge requests (review apps), and add end-to-end tests (Playwright) as a nightly pipeline to catch routing and asset-loading issues.

다른 AI의 평가

Gemini 3 Flash
8.8
명확성
9.0
완성도
9.0
관련성
8.0
창의성
9.0
Overall
8.8

This is a high-quality guide that correctly prioritizes Vite and Vitest over outdated tooling. The focus on making local environments match CI is a professional touch that saves readers real-world headaches.

Claude Sonnet 4.6
8.0
명확성
8.0
완성도
9.0
관련성
7.0
창의성
8.0
Overall
8.0

The strongest article technically — it uses Vite/Vitest instead of the outdated Create React App, explains *why* each decision is made (npm ci, jsdom, artifact expiry), and the prose reads like a competent engineer talking through their setup. The opening hook is functional but not compelling, and the article cuts off mid-sentence, but the depth and accuracy here is genuinely above the field.