Notice
Recent Posts
250x250
«   2026/04   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30
관리 메뉴

일상 코딩

[윈도우 개발 환경 설정] 4편: Node.js & React SPA 환경 구축 본문

Windows 개발환경 세팅

[윈도우 개발 환경 설정] 4편: Node.js & React SPA 환경 구축

polarcompass 2026. 3. 31. 22:24
728x90

이제 모든 최신 정보를 확인했으므로 4편을 작성하겠습니다.


4편: Node.js & React SPA 환경 구축

시리즈: 윈도우 네이티브 개발 환경 세팅부터 클라우드 배포까지 (4/14)

이전 편 전제: Windows Terminal, PowerShell 7, winget, Chocolatey, Nerd Font, Git, GitHub CLI, Google Antigravity 설치 완료


들어가며

프론트엔드 개발의 첫걸음은 Node.js다. 이번 편에서는 nvm-windows로 Node.js 버전을 관리하고, Vite 기반으로 React + TypeScript + Tailwind CSS 프로젝트를 생성한다. React Router로 페이지 라우팅을 잡고, React.lazySuspense로 코드 스플리팅까지 적용한 뒤, 개발 서버 실행과 프로덕션 빌드 확인까지 완료하는 것이 목표다.

모든 작업은 PowerShell 7(Windows Terminal)에서 진행하며, WSL은 사용하지 않는다.


1. nvm-windows로 Node.js 설치

Node.js를 직접 설치해도 되지만, 프로젝트마다 다른 Node 버전이 필요한 상황은 반드시 온다. nvm-windows를 사용하면 한 줄로 버전을 전환할 수 있다.

1-1. nvm-windows 설치

Chocolatey로 설치한다. PowerShell 7을 관리자 권한으로 실행한 뒤 아래 명령어를 입력한다.

choco install nvm -y

설치가 끝나면 터미널을 완전히 종료했다가 다시 열어야 환경변수가 반영된다. 새 터미널에서 버전을 확인한다.

nvm version

버전 번호(예: 1.2.2)가 출력되면 정상이다.

1-2. Node.js LTS 설치 & 활성화

현재 LTS는 v24 계열이다(코드명 Krypton). 설치하고 활성화한다.

nvm install lts
nvm use lts

참고: nvm install lts가 동작하지 않는 nvm-windows 구 버전에서는 nvm install 24.14.1처럼 구체적인 버전 번호를 지정해야 한다.

설치 확인:

node -v
npm -v

node -vv24.x.x, npm -v10.x.x 이상으로 출력되면 성공이다.

1-3. npm 글로벌 설정

npm의 초기화 기본값을 설정해 두면 매번 npm init 할 때 편하다.

npm config set init-author-name "Your Name"
npm config set init-license "MIT"

설정이 저장된 파일 위치를 확인하려면:

npm config list

2. Vite로 React + TypeScript 프로젝트 생성

Vite는 현재 v8이 최신 안정 버전이다. npm create vite@latest 명령 한 줄로 프로젝트를 스캐폴딩한다.

2-1. 프로젝트 생성

npm create vite@latest my-react-app -- --template react-ts

--template react-ts를 붙이면 인터랙티브 선택 없이 바로 React + TypeScript 템플릿이 적용된다. 프로젝트 폴더로 이동하고 의존성을 설치한다.

cd my-react-app
npm install

2-2. 프로젝트 구조 확인

생성된 프로젝트의 주요 파일 구조는 다음과 같다.

my-react-app/
├── index.html
├── package.json
├── tsconfig.json
├── tsconfig.app.json
├── tsconfig.node.json
├── vite.config.ts
├── public/
│   └── vite.svg
└── src/
    ├── main.tsx          ← 엔트리 포인트
    ├── App.tsx
    ├── App.css
    ├── index.css
    └── vite-env.d.ts

vite.config.ts를 열어보면 기본 설정은 매우 간결하다.

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

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

3. Tailwind CSS v4 설치

Tailwind CSS v4부터는 설정 파일(tailwind.config.js)이 사라지고, Vite 플러그인 방식으로 통합된다. 설치가 훨씬 간결해졌다.

3-1. 패키지 설치

npm install tailwindcss @tailwindcss/vite

3-2. Vite 플러그인 등록

vite.config.ts를 아래와 같이 수정한다.

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'

export default defineConfig({
  plugins: [
    react(),
    tailwindcss(),
  ],
})

3-3. CSS에 Tailwind import 추가

src/index.css 파일의 맨 위에 아래 한 줄을 추가한다. 기존 내용은 모두 지워도 된다.

@import "tailwindcss";

3-4. 동작 확인

src/App.tsx의 내용을 아래처럼 간단히 바꿔서 Tailwind가 적용되는지 확인한다.

function App() {
  return (
    <div className="min-h-screen flex items-center justify-center bg-gray-100">
      <h1 className="text-4xl font-bold text-blue-600">
        Hello, Tailwind CSS v4!
      </h1>
    </div>
  )
}

export default App

개발 서버를 실행한다.

npm run dev

브라우저에서 http://localhost:5173을 열어 파란색 볼드 텍스트가 화면 중앙에 표시되면 Tailwind 설정이 완료된 것이다. 확인 후 Ctrl+C로 서버를 종료한다.


4. React Router v7 설정

React Router는 현재 v7이 최신이다. v7에는 Framework 모드, Data 모드, Declarative 모드 세 가지가 있는데, 우리는 Vite 기반 SPA를 직접 관리할 것이므로 Declarative 모드를 사용한다. 이 모드는 기존 React Router v6의 사용 방식과 거의 동일하다.

4-1. 패키지 설치

npm install react-router

참고: v7부터는 react-router 하나만 설치하면 된다. react-router-dom을 별도로 설치할 필요가 없다.

4-2. 페이지 컴포넌트 생성

라우팅 대상이 될 페이지를 만든다. src/pages/ 디렉토리를 생성하고, 세 개의 페이지를 만든다.

mkdir src/pages

src/pages/Home.tsx:

export default function Home() {
  return (
    <div className="p-8">
      <h1 className="text-3xl font-bold">Home</h1>
      <p className="mt-4 text-gray-600">메인 페이지입니다.</p>
    </div>
  )
}

src/pages/About.tsx:

export default function About() {
  return (
    <div className="p-8">
      <h1 className="text-3xl font-bold">About</h1>
      <p className="mt-4 text-gray-600">소개 페이지입니다.</p>
    </div>
  )
}

src/pages/NotFound.tsx:

export default function NotFound() {
  return (
    <div className="p-8 text-center">
      <h1 className="text-5xl font-bold text-red-500">404</h1>
      <p className="mt-4 text-gray-600">페이지를 찾을 수 없습니다.</p>
    </div>
  )
}

4-3. 레이아웃 컴포넌트 생성

공통 레이아웃(네비게이션 바 등)을 담을 컴포넌트를 만든다.

src/layouts/DefaultLayout.tsx:

mkdir src/layouts
import { Link, Outlet } from 'react-router'

export default function DefaultLayout() {
  return (
    <div className="min-h-screen bg-gray-50">
      <nav className="bg-white shadow p-4 flex gap-6">
        <Link to="/" className="text-blue-600 hover:underline font-medium">
          Home
        </Link>
        <Link to="/about" className="text-blue-600 hover:underline font-medium">
          About
        </Link>
      </nav>
      <main>
        <Outlet />
      </main>
    </div>
  )
}

<Outlet />은 자식 라우트의 컴포넌트가 렌더링되는 자리다.

4-4. 라우터 구성 & 엔트리 포인트 수정

src/App.tsx를 라우트 정의 파일로 교체한다.

import { BrowserRouter, Routes, Route } from 'react-router'
import DefaultLayout from './layouts/DefaultLayout'
import Home from './pages/Home'
import About from './pages/About'
import NotFound from './pages/NotFound'

export default function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route element={<DefaultLayout />}>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="*" element={<NotFound />} />
        </Route>
      </Routes>
    </BrowserRouter>
  )
}

src/main.tsx는 그대로 둔다. 기본 생성된 코드가 <App />을 렌더링하고 있으므로 별도 수정이 필요 없다.

import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

개발 서버를 실행하고 http://localhost:5173, http://localhost:5173/about, http://localhost:5173/anything에 각각 접속해 라우팅이 정상 동작하는지 확인한다.

npm run dev

5. 코드 스플리팅 (React.lazy & Suspense)

SPA에서 모든 페이지 코드를 한 번에 번들링하면 초기 로딩이 느려진다. React.lazy로 페이지 컴포넌트를 동적 import 하면, Vite가 자동으로 페이지별 청크 파일을 분리한다. 사용자가 해당 페이지에 접근할 때 비로소 해당 청크가 로드된다.

5-1. App.tsx에 lazy 적용

import { lazy, Suspense } from 'react'
import { BrowserRouter, Routes, Route } from 'react-router'
import DefaultLayout from './layouts/DefaultLayout'

const Home = lazy(() => import('./pages/Home'))
const About = lazy(() => import('./pages/About'))
const NotFound = lazy(() => import('./pages/NotFound'))

export default function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<div className="p-8 text-center">Loading...</div>}>
        <Routes>
          <Route element={<DefaultLayout />}>
            <Route path="/" element={<Home />} />
            <Route path="/about" element={<About />} />
            <Route path="*" element={<NotFound />} />
          </Route>
        </Routes>
      </Suspense>
    </BrowserRouter>
  )
}

변경 포인트는 세 가지다.

첫째, 기존의 정적 importlazy(() => import(...))로 교체했다. 둘째, <Suspense><Routes>를 감싸서, 컴포넌트가 로드되는 동안 보여줄 폴백 UI를 지정했다. 셋째, DefaultLayout은 모든 페이지에 공통으로 쓰이므로 lazy 처리하지 않고 정적 import를 유지했다. 항상 필요한 컴포넌트까지 lazy로 만들면 오히려 불필요한 로딩 지연이 생긴다.

5-2. 코드 스플리팅 확인

프로덕션 빌드를 실행하면 청크가 분리되는 것을 직접 확인할 수 있다.

npm run build

빌드 출력에서 아래와 비슷한 결과가 나타난다.

dist/assets/Home-BxK3w2Lq.js       0.25 kB │ gzip: 0.20 kB
dist/assets/About-Da4F1hKe.js      0.24 kB │ gzip: 0.19 kB
dist/assets/NotFound-Ck9jR2mT.js   0.28 kB │ gzip: 0.22 kB
dist/assets/index-Hd8sK1Qw.js     142.50 kB │ gzip: 46.10 kB

페이지별로 별도의 JS 파일이 생성된 것을 확인할 수 있다. index-*.js는 React, React Router 등 공통 라이브러리가 포함된 메인 번들이다.


6. 개발 서버 실행 확인

npm run dev

터미널에 아래와 같은 출력이 나타난다.

  VITE v8.x.x  ready in xxx ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: http://192.168.x.x:5173/
  ➜  press h + enter to show help

브라우저에서 확인할 항목은 다음과 같다.

http://localhost:5173/에 접속하면 Home 페이지가 표시되고, 네비게이션 바의 About 링크를 클릭하면 페이지 전환이 일어난다(전체 새로고침 없이 SPA 방식). http://localhost:5173/anything처럼 존재하지 않는 경로에 접속하면 404 페이지가 표시된다. 브라우저 개발자 도구의 Network 탭에서 About 페이지로 이동할 때 About-*.js 청크가 별도로 로드되는 것을 확인할 수 있다.


7. 프로덕션 빌드 & 정적 파일 확인

7-1. 빌드

npm run build

빌드 결과물은 dist/ 폴더에 생성된다.

7-2. 빌드 결과 구조

dist/
├── index.html
├── assets/
│   ├── index-Hd8sK1Qw.js
│   ├── index-B3k1wR4p.css
│   ├── Home-BxK3w2Lq.js
│   ├── About-Da4F1hKe.js
│   └── NotFound-Ck9jR2mT.js
└── vite.svg

dist/ 폴더 전체가 CDN이나 정적 파일 서버에 그대로 업로드되는 결과물이다. 서버 사이드 런타임이 필요 없다.

7-3. 로컬에서 빌드 결과 미리보기

Vite에 내장된 preview 명령으로 빌드 결과물을 로컬에서 서빙할 수 있다.

npm run preview
  ➜  Local:   http://localhost:4173/

http://localhost:4173/에 접속해서 개발 서버와 동일하게 동작하는지 확인한다. 이 미리보기 서버는 dist/ 폴더를 정적으로 서빙하는 것이므로, 실제 배포 환경과 거의 동일한 조건에서 테스트하는 셈이다.

7-4. SPA 라우팅과 배포 시 주의사항

SPA는 모든 경로가 index.html 하나로 처리되어야 한다. 사용자가 /about에서 브라우저를 새로고침하면, 서버는 /about이라는 실제 파일을 찾으려 하고 404를 반환한다.

이 문제는 배포 환경의 웹 서버에서 모든 경로를 index.html로 폴백 시키는 설정으로 해결한다. 이 시리즈의 11편(Caddy)에서 리눅스 서버에 이 설정을 적용할 예정이다. 지금은 "SPA 배포 시 폴백 설정이 필요하다"는 것만 기억해 두자.


8. 최종 프로젝트 구조 정리

여기까지 완료한 프로젝트의 전체 구조다.

my-react-app/
├── index.html
├── package.json
├── tsconfig.json
├── tsconfig.app.json
├── tsconfig.node.json
├── vite.config.ts
├── public/
│   └── vite.svg
├── dist/                      ← 빌드 결과물 (git에 포함하지 않음)
└── src/
    ├── main.tsx
    ├── App.tsx                ← 라우트 정의 + lazy/Suspense
    ├── index.css              ← @import "tailwindcss"
    ├── vite-env.d.ts
    ├── layouts/
    │   └── DefaultLayout.tsx  ← 공통 레이아웃 (네비게이션 + Outlet)
    └── pages/
        ├── Home.tsx
        ├── About.tsx
        └── NotFound.tsx

9. 최종 확인 체크리스트

아래 항목을 모두 통과하면 4편이 완료된 것이다.

# 1. nvm & Node.js 버전 확인
nvm version
node -v
npm -v

# 2. 개발 서버 실행
cd my-react-app
npm run dev
# → http://localhost:5173/ 에서 Home 페이지 표시
# → /about 으로 이동 시 About 페이지 표시 (새로고침 없음)
# → /anything 으로 이동 시 404 페이지 표시

# 3. Tailwind CSS 동작 확인
# → 페이지에 Tailwind 유틸리티 클래스 스타일이 적용되어 있음

# 4. 프로덕션 빌드
npm run build
# → dist/ 폴더 생성
# → 페이지별 JS 청크 파일 분리 확인 (Home-*.js, About-*.js 등)

# 5. 빌드 결과 미리보기
npm run preview
# → http://localhost:4173/ 에서 빌드 결과물 정상 서빙 확인
확인 항목 기대 결과
node -v v24.x.x
npm -v 10.x.x 이상
npm run dev http://localhost:5173 접속 가능
Tailwind 스타일 유틸리티 클래스 정상 적용
React Router /, /about, /* 라우팅 동작
코드 스플리팅 빌드 시 페이지별 JS 청크 분리
npm run preview http://localhost:4173 접속 가능

10. 원클릭 자동화 스크립트

매번 위 과정을 수동으로 반복하기 번거롭다면, 아래 PowerShell 스크립트 하나로 전체 세팅을 자동화할 수 있다. 스크립트 파일을 만들고 실행하면 nvm-windows 설치부터 프로젝트 생성, Tailwind·React Router 설치, 파일 생성까지 한 번에 끝난다.

10-1. 스크립트 파일 생성

아래 내용을 setup-react-spa.ps1로 저장한다.

<#
  setup-react-spa.ps1
  4편 자동화 스크립트: Node.js + React SPA (Vite + TypeScript + Tailwind + React Router)
  실행: 관리자 권한 PowerShell 7에서 .\setup-react-spa.ps1
  인자: -ProjectName <이름>  (기본값: my-react-app)
#>

param(
    [string]$ProjectName = "my-react-app"
)

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"

function Write-Step {
    param([string]$Message)
    Write-Host "`n========================================" -ForegroundColor Cyan
    Write-Host " $Message" -ForegroundColor Cyan
    Write-Host "========================================" -ForegroundColor Cyan
}

# ─────────────────────────────────────────────
# 1. nvm-windows 설치 확인
# ─────────────────────────────────────────────
Write-Step "1/8 nvm-windows 확인"

if (-not (Get-Command nvm -ErrorAction SilentlyContinue)) {
    Write-Host "nvm-windows가 설치되어 있지 않습니다. Chocolatey로 설치합니다..." -ForegroundColor Yellow
    choco install nvm -y
    # 환경변수 새로고침
    $env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User")
    if (-not (Get-Command nvm -ErrorAction SilentlyContinue)) {
        Write-Host "nvm 설치 후 터미널을 재시작해야 합니다. 스크립트를 종료합니다." -ForegroundColor Red
        exit 1
    }
}
Write-Host "nvm version: $(nvm version)" -ForegroundColor Green

# ─────────────────────────────────────────────
# 2. Node.js LTS 설치 & 활성화
# ─────────────────────────────────────────────
Write-Step "2/8 Node.js LTS 설치"

$installedList = nvm list 2>&1 | Out-String
if ($installedList -notmatch "24\.") {
    Write-Host "Node.js v24 LTS를 설치합니다..." -ForegroundColor Yellow
    nvm install lts
}
nvm use lts

Write-Host "node: $(node -v)" -ForegroundColor Green
Write-Host "npm:  $(npm -v)" -ForegroundColor Green

# ─────────────────────────────────────────────
# 3. npm 글로벌 기본값 설정
# ─────────────────────────────────────────────
Write-Step "3/8 npm 기본값 설정"

npm config set init-license "MIT"
Write-Host "npm init-license: MIT" -ForegroundColor Green

# ─────────────────────────────────────────────
# 4. Vite로 React + TypeScript 프로젝트 생성
# ─────────────────────────────────────────────
Write-Step "4/8 Vite 프로젝트 생성: $ProjectName"

if (Test-Path $ProjectName) {
    Write-Host "'$ProjectName' 폴더가 이미 존재합니다. 건너뜁니다." -ForegroundColor Yellow
} else {
    npm create vite@latest $ProjectName -- --template react-ts
}

Set-Location $ProjectName

# ─────────────────────────────────────────────
# 5. 의존성 설치 (Tailwind CSS v4 + React Router v7)
# ─────────────────────────────────────────────
Write-Step "5/8 의존성 설치"

npm install
npm install tailwindcss @tailwindcss/vite
npm install react-router

Write-Host "모든 의존성 설치 완료" -ForegroundColor Green

# ─────────────────────────────────────────────
# 6. vite.config.ts 수정
# ─────────────────────────────────────────────
Write-Step "6/8 vite.config.ts 수정"

$viteConfig = @"
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'

export default defineConfig({
  plugins: [
    react(),
    tailwindcss(),
  ],
})
"@

Set-Content -Path "vite.config.ts" -Value $viteConfig -Encoding UTF8
Write-Host "vite.config.ts 작성 완료" -ForegroundColor Green

# ─────────────────────────────────────────────
# 7. 소스 파일 생성
# ─────────────────────────────────────────────
Write-Step "7/8 소스 파일 생성"

# index.css
Set-Content -Path "src/index.css" -Value '@import "tailwindcss";' -Encoding UTF8
Write-Host "  src/index.css" -ForegroundColor Gray

# 디렉토리 생성
New-Item -ItemType Directory -Path "src/pages" -Force | Out-Null
New-Item -ItemType Directory -Path "src/layouts" -Force | Out-Null

# src/pages/Home.tsx
$homePage = @"
export default function Home() {
  return (
    <div className="p-8">
      <h1 className="text-3xl font-bold">Home</h1>
      <p className="mt-4 text-gray-600">메인 페이지입니다.</p>
    </div>
  )
}
"@
Set-Content -Path "src/pages/Home.tsx" -Value $homePage -Encoding UTF8
Write-Host "  src/pages/Home.tsx" -ForegroundColor Gray

# src/pages/About.tsx
$aboutPage = @"
export default function About() {
  return (
    <div className="p-8">
      <h1 className="text-3xl font-bold">About</h1>
      <p className="mt-4 text-gray-600">소개 페이지입니다.</p>
    </div>
  )
}
"@
Set-Content -Path "src/pages/About.tsx" -Value $aboutPage -Encoding UTF8
Write-Host "  src/pages/About.tsx" -ForegroundColor Gray

# src/pages/NotFound.tsx
$notFoundPage = @"
export default function NotFound() {
  return (
    <div className="p-8 text-center">
      <h1 className="text-5xl font-bold text-red-500">404</h1>
      <p className="mt-4 text-gray-600">페이지를 찾을 수 없습니다.</p>
    </div>
  )
}
"@
Set-Content -Path "src/pages/NotFound.tsx" -Value $notFoundPage -Encoding UTF8
Write-Host "  src/pages/NotFound.tsx" -ForegroundColor Gray

# src/layouts/DefaultLayout.tsx
$defaultLayout = @"
import { Link, Outlet } from 'react-router'

export default function DefaultLayout() {
  return (
    <div className="min-h-screen bg-gray-50">
      <nav className="bg-white shadow p-4 flex gap-6">
        <Link to="/" className="text-blue-600 hover:underline font-medium">
          Home
        </Link>
        <Link to="/about" className="text-blue-600 hover:underline font-medium">
          About
        </Link>
      </nav>
      <main>
        <Outlet />
      </main>
    </div>
  )
}
"@
Set-Content -Path "src/layouts/DefaultLayout.tsx" -Value $defaultLayout -Encoding UTF8
Write-Host "  src/layouts/DefaultLayout.tsx" -ForegroundColor Gray

# src/App.tsx (lazy + Suspense)
$appTsx = @"
import { lazy, Suspense } from 'react'
import { BrowserRouter, Routes, Route } from 'react-router'
import DefaultLayout from './layouts/DefaultLayout'

const Home = lazy(() => import('./pages/Home'))
const About = lazy(() => import('./pages/About'))
const NotFound = lazy(() => import('./pages/NotFound'))

export default function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<div className="p-8 text-center">Loading...</div>}>
        <Routes>
          <Route element={<DefaultLayout />}>
            <Route path="/" element={<Home />} />
            <Route path="/about" element={<About />} />
            <Route path="*" element={<NotFound />} />
          </Route>
        </Routes>
      </Suspense>
    </BrowserRouter>
  )
}
"@
Set-Content -Path "src/App.tsx" -Value $appTsx -Encoding UTF8
Write-Host "  src/App.tsx" -ForegroundColor Gray

# src/main.tsx
$mainTsx = @"
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />
  </StrictMode>,
)
"@
Set-Content -Path "src/main.tsx" -Value $mainTsx -Encoding UTF8
Write-Host "  src/main.tsx" -ForegroundColor Gray

# 불필요한 기본 파일 정리
Remove-Item -Path "src/App.css" -ErrorAction SilentlyContinue

Write-Host "모든 소스 파일 생성 완료" -ForegroundColor Green

# ─────────────────────────────────────────────
# 8. 빌드 테스트
# ─────────────────────────────────────────────
Write-Step "8/8 프로덕션 빌드 테스트"

npm run build

if (Test-Path "dist/index.html") {
    Write-Host "`n빌드 성공! dist/ 폴더가 생성되었습니다." -ForegroundColor Green
} else {
    Write-Host "`n빌드 실패. 에러 로그를 확인하세요." -ForegroundColor Red
    exit 1
}

# ─────────────────────────────────────────────
# 완료 메시지
# ─────────────────────────────────────────────
Write-Host "`n" -NoNewline
Write-Host "============================================" -ForegroundColor Green
Write-Host " React SPA 프로젝트 세팅 완료!" -ForegroundColor Green
Write-Host "============================================" -ForegroundColor Green
Write-Host ""
Write-Host "  프로젝트 경로:  $(Get-Location)" -ForegroundColor White
Write-Host "  개발 서버:      npm run dev" -ForegroundColor White
Write-Host "  빌드:           npm run build" -ForegroundColor White
Write-Host "  빌드 미리보기:  npm run preview" -ForegroundColor White
Write-Host ""

10-2. 스크립트 실행

관리자 권한 PowerShell 7에서 실행한다.

# 기본 프로젝트 이름(my-react-app)으로 생성
.\setup-react-spa.ps1

# 커스텀 프로젝트 이름으로 생성
.\setup-react-spa.ps1 -ProjectName "my-awesome-app"

Execution Policy 에러가 발생하면: Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned를 먼저 실행한다. (1편에서 이미 설정했다면 생략)

스크립트가 완료되면 8단계 모두 녹색 메시지로 결과가 출력되며, dist/ 폴더까지 생성된 상태로 마무리된다. 이후 npm run dev로 개발 서버를 바로 시작할 수 있다.


다음 편 예고

5편: Go & Gin 환경 구축에서는 winget으로 Go를 설치하고, 환경변수를 확인한 뒤, Gin 프레임워크로 Hello World API 서버를 실행한다. 프론트엔드(React SPA)와 백엔드(Go API)가 분리된 구조의 첫 번째 백엔드를 세팅하게 된다.

728x90