이번 편의 결과물: bookshelf가 실제 URL로 배포되어 브라우저에서 접속됩니다. · 다루는 개념: 프로덕션 빌드, .env 환경 변수 분리, Vercel 배포, GitHub Pages 대안 배포
이 편에서 만드는 파일
bookshelf/
├── .env.development + 개발용 환경 변수
├── .env.production + 배포용 환경 변수
├── vercel.json + Vercel 리라이트 설정
├── vite.config.js ~ GitHub Pages용 base 경로 분기
├── package.json ~ deploy:gh-pages 스크립트, gh-pages 의존성 추가
└── src/api/
├── client.js + 08편의 apiFetch를 books.js에서 분리한 공용 모듈
├── books.js ~ client.js의 apiFetch 사용
└── auth.js ~ client.js의 apiFetch 사용개념 정리
환경 변수는 빌드 시점에 굳는다
Vite는 import.meta.env.VITE_* 형태의 값을 빌드하는 순간 실제 문자열로 치환합니다. 실행 중에 읽는 것이 아니라 번들 파일 안에 값이 그대로 박힙니다. 배포 후 값을 바꾸면 다음 빌드부터 반영됩니다. VITE_ 접두사가 없는 변수는 브라우저 번들에 포함되지 않습니다.
| 파일 | 적용 시점 | 용도 |
|---|---|---|
.env.development | npm run dev | 로컬 개발용 값 |
.env.production | npm run build | 배포용 값 |
.env.local | 항상(git 무시 대상) | 개인별 비공개 값 |
이 과목의 배포가 갖는 한계
01편 학습 방향에서 이미 밝혔듯, 이 과목은 실서버 구현을 다루지 않습니다. 04편부터 백엔드로 써온 json-server는 학습자의 로컬 컴퓨터에서만 실행되는 개발용 도구라, Vercel에 올린 정적 프런트엔드가 그 주소(http://localhost:3001)에 접근할 수 없습니다. 그래서 배포된 데모는 화면과 빌드 파이프라인까지 실제로 동작하는 것을 목표로 하고, “로그인 후 책 목록 조회”처럼 백엔드가 필요한 기능은 배포 URL을 연 사람이 자신의 컴퓨터에서 json-server를 함께 띄워야 확인할 수 있다는 점을 안내합니다. VITE_API_BASE_URL을 환경 변수로 분리해 두면, 이후 실제 서버를 마련했을 때 이 값만 바꿔 그대로 연결할 수 있습니다.
정적 호스팅과 SPA 라우팅
bookshelf의 dist/ 폴더는 index.html과 해시가 붙은 JS·CSS뿐인 정적 파일입니다. react-router가 만드는 /books/3 같은 경로는 실제 파일로 존재하지 않아, 이 경로에서 새로고침하면 정적 서버가 파일을 못 찾아 404를 돌려줍니다. 해법은 어떤 경로든 못 찾으면 index.html을 200으로 돌려주는 리라이트 규칙입니다.
실습
1. 환경 변수 파일 만들기
# .env.development
VITE_API_BASE_URL=http://localhost:3001# .env.production
VITE_API_BASE_URL=http://localhost:3001두 값을 같게 둔 이유는 이 과목이 실제 호스팅된 백엔드를 마련하지 않기 때문입니다. 실제 서버를 붙이면 .env.production의 값만 그 서버 도메인으로 바꾸면 됩니다.
2. api/client.js로 apiFetch 공용화
08편에서 api/books.js 안에 직접 두었던 apiFetch를 별도 모듈로 옮기고, 하드코딩된 URL을 환경 변수로 바꿉니다.
// src/api/client.js
import { getToken, clearToken } from './auth.js'
const API_BASE = import.meta.env.VITE_API_BASE_URL
export async function apiFetch(path, options = {}) {
const token = getToken()
if (!token || token === 'expired-token') {
clearToken()
throw new Error('SESSION_EXPIRED')
}
const headers = { 'Content-Type': 'application/json', ...options.headers }
const response = await fetch(`${API_BASE}${path}`, { ...options, headers })
if (!response.ok) {
throw new Error(`요청이 실패했습니다 (상태 코드: ${response.status})`)
}
return response
}// src/api/books.js (발췌 — 함수 본문은 04~12편 코드 유지, import만 변경)
import { apiFetch } from './client.js'
export async function getBooks(params) {
const response = await apiFetch(`/books?${new URLSearchParams(params)}`)
return response.json()
}auth.js의 login도 같은 API_BASE를 쓰도록 import.meta.env.VITE_API_BASE_URL로 바꿉니다. 세션 만료 검사가 필요 없는 login 요청은 apiFetch를 거치지 않고 지금처럼 fetch를 직접 씁니다.
3. 프로덕션 빌드와 로컬 리허설
npm run build
npm run previewnpm run preview가 알려주는 주소(기본 http://localhost:4173)에 접속하기 전에, 다른 터미널에서 npx json-server db.json --port 3001을 함께 켭니다. 개발 서버에서는 안 보이던 문제(정적 자산 경로 등)가 여기서 드러납니다.
4. Vercel에 배포하기
vercel.json으로 SPA 리라이트를 선언합니다.
// vercel.json
{
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
}- Vercel 대시보드에서
New Project→bookshelf저장소 선택. - Framework Preset이
Vite로 자동 인식되는지 확인(빌드 명령npm run build, 출력 폴더dist). - Environment Variables에
VITE_API_BASE_URL을 등록. Deploy클릭 → 완료 후https://bookshelf-xxxx.vercel.app형태의 URL 발급.
npx vercel --prod이후 main 브랜치에 push할 때마다 자동으로 재배포되고, 다른 브랜치는 미리보기 URL을 별도로 받습니다.
5. GitHub Pages 대안 배포
npm install --save-dev gh-pages cross-envGitHub Pages는 저장소 이름이 경로에 포함되므로(https://사용자명.github.io/bookshelf/) base 경로를 지정해야 합니다.
// vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
base: process.env.DEPLOY_TARGET === 'gh-pages' ? '/bookshelf/' : '/',
})// package.json (scripts 발췌)
{
"scripts": {
"build": "vite build",
"build:gh-pages": "cross-env DEPLOY_TARGET=gh-pages vite build",
"deploy:gh-pages": "npm run build:gh-pages && gh-pages -d dist"
}
}npm run deploy:gh-pagesgh-pages 브랜치에 dist/ 내용을 올리고, 저장소 설정의 Settings → Pages에서 그 브랜치를 배포 소스로 지정하면 https://사용자명.github.io/bookshelf/로 접속됩니다. GitHub Pages는 SPA 리라이트를 기본 지원하지 않으므로, public/404.html에 index.html과 같은 내용을 복사해 두는 방식이 흔한 우회책입니다. 이 과목은 Vercel을 기본으로 삼으므로 GitHub Pages는 대안 절차로만 다룹니다.
직접 해보기
Vercel과 GitHub Pages에 배포된 두 URL 모두에서 /books/3처럼 하위 경로로 직접 접속해 새로고침해 보고, 결과를 비교해 보세요.
Vercel은 vercel.json의 리라이트 덕에 정상적으로 페이지가 나옵니다(로그인 화면으로 리다이렉트되더라도 404는 아닙니다). GitHub Pages는 public/404.html 우회책을 추가하지 않았다면 404가 뜨는 것을 확인할 수 있습니다.
자주 하는 실수
| 증상 | 원인 | 고치는 법 |
|---|---|---|
| 배포 후 화면이 하얗게 뜨고 콘솔에 자산 404 | GitHub Pages인데 base 경로를 안 맞춤 | vite.config.js의 base를 저장소 이름과 일치시킴 |
| 환경 변수를 바꿨는데 배포에 반영 안 됨 | 값 변경 후 재배포 안 함 | 값은 다음 빌드부터 적용, 재배포까지 한 세트 |
| 배포된 사이트에서 로그인이 항상 실패 | 로컬에서 json-server를 안 띄움 | VITE_API_BASE_URL이 가리키는 서버를 직접 실행 |