React + Node.js 풀스택 앱을 무료로 배포하는 방법은 크게 두 가지다. 백엔드를 Vercel Serverless로 처리하는 방법과 Railway에 독립 서버로 올리는 방법이다. 각각 장단점이 다르므로 프로젝트 성격에 맞게 선택하면 된다.
두 방법 비교
| 항목 | Vercel (Serverless) | Railway (Express 서버) |
|---|---|---|
| 백엔드 형태 | Serverless Function | 항상 켜진 Express 서버 |
| 콜드 스타트 | 있음 (첫 요청 2~5초) | 없음 |
| Express 구조 변경 | 필요 (listen() 제거) | 불필요 (그대로 사용) |
| WebSocket | 불가 | 가능 |
| 무료 한도 | 넉넉함 | 월 $5 크레딧 제공 |
| 적합한 경우 | 간단한 REST API | 복잡한 서버, 상태 유지 필요 시 |
간단한 CRUD API라면 Vercel Serverless로 충분하다. WebSocket이 필요하거나 Express 구조를 그대로 유지하고 싶다면 Railway가 낫다.
공통: MongoDB Atlas 설정
두 방법 모두 DB는 MongoDB Atlas M0 무료 티어를 쓴다.
클러스터 생성
- mongodb.com/atlas → 회원가입
- Create a deployment → M0 Free 선택
- Provider: AWS, Region: Seoul(
ap-northeast-2) 선택 - Cluster Name 입력 → Create Deployment
DB 유저 생성
Database Access → Add New Database User
- Authentication: Password
- Username / Password 설정
- Role: Read and write to any database
네트워크 접근 허용
Network Access → Add IP Address → Allow Access from Anywhere (0.0.0.0/0)
Vercel과 Railway 모두 서버 IP가 고정되어 있지 않아서 전체 허용이 필요하다.
Connection String 복사
Database → Connect → Drivers → connection string 복사
mongodb+srv://<username>:<password>@cluster0.xxxxx.mongodb.net/<dbname>?retryWrites=true&w=majority방법 1: Vercel Serverless (프론트 + 백엔드 통합)
Express 앱을 Serverless Function으로 변환해서 Vercel 하나로 처리한다.
백엔드 구조 변경
backend/
├── api/
│ └── index.js ← Vercel 진입점
├── src/
│ ├── app.js ← app.listen() 제거, app만 export
│ ├── routes/
│ └── models/
├── vercel.json
└── package.jsonapp.js에서 listen() 제거:
// src/app.js
import express from 'express'
import cors from 'cors'
import mongoose from 'mongoose'
const app = express()
app.use(cors({
origin: process.env.FRONTEND_URL || 'http://localhost:5173',
credentials: true,
}))
app.use(express.json())
mongoose.connect(process.env.MONGODB_URI)
app.use('/api/auth', authRouter)
app.use('/api/plans', plansRouter)
export default app // listen() 없이 export만
api/index.js 생성:
import app from '../src/app.js'
export default app
vercel.json 생성:
{
"version": 2,
"builds": [{ "src": "api/index.js", "use": "@vercel/node" }],
"routes": [{ "src": "/api/(.*)", "dest": "api/index.js" }]
}
Vercel 배포
백엔드
- Vercel → Add New Project → 저장소 선택
- Root Directory:
backend - Environment Variables:
MONGODB_URI = mongodb+srv://...
JWT_SECRET = 랜덤 문자열
FRONTEND_URL = https://your-frontend.vercel.app프론트엔드
- Add New Project → 같은 저장소 선택
- Root Directory:
frontend - Environment Variables:
VITE_API_URL = https://your-backend.vercel.app/apiVercel Serverless 주의사항
콜드 스타트: 일정 시간 요청이 없으면 인스턴스가 내려간다. 다음 첫 요청 시 DB 연결부터 시작해 2~5초 지연이 생긴다. mongoose 연결을 캐싱하면 같은 인스턴스 내 재요청 속도를 높일 수 있다.
// src/db.js — mongoose 연결 캐싱
let cached = global.mongoose || { conn: null, promise: null }
global.mongoose = cached
export async function connectDB() {
if (cached.conn) return cached.conn
if (!cached.promise) {
cached.promise = mongoose.connect(process.env.MONGODB_URI)
}
cached.conn = await cached.promise
return cached.conn
}
WebSocket 불가: Serverless는 요청-응답 구조라 지속 연결이 필요한 WebSocket을 지원하지 않는다.
방법 2: Railway (독립 Express 서버)
Express 구조를 그대로 유지하면서 Railway에 서버를 올린다. 코드 수정이 거의 없다.
railway.json 생성 (선택)
Railway는 package.json의 scripts를 자동으로 감지하지만, 명시적으로 설정하고 싶다면 추가한다.
{
"$schema": "https://railway.app/railway.schema.json",
"build": {
"builder": "NIXPACKS"
},
"deploy": {
"startCommand": "node src/server.js",
"restartPolicyType": "ON_FAILURE"
}
}
포트 설정
Railway는 환경 변수 PORT를 자동으로 주입한다. 서버 코드에서 반드시 이 값을 사용해야 한다.
// src/server.js
const PORT = process.env.PORT || 5001
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`)
})
Railway 배포
- railway.app → 회원가입 (GitHub 연동)
- New Project → Deploy from GitHub repo → 저장소 선택
- Root Directory:
backend - Variables 탭에서 환경 변수 설정:
NODE_ENV = production
MONGODB_URI = mongodb+srv://...
JWT_SECRET = 랜덤 문자열
GOOGLE_MAPS_API_KEY = ...
ODSAY_API_KEY = ...
CORS_ORIGIN = https://your-frontend.vercel.app- Networking → Public Networking → Generate Domain (또는 Custom Domain)
- 생성된 URL 확인:
https://your-backend.up.railway.app
Vercel 프론트엔드 배포
백엔드 Railway URL을 환경 변수에 입력한다.
VITE_API_URL = https://your-backend.up.railway.app/apiCORS 설정
Railway 배포 시 CORS_ORIGIN 환경 변수를 활용한다.
// src/app.js
app.use(cors({
origin: [
'http://localhost:5173',
process.env.CORS_ORIGIN, // Vercel 프론트엔드 URL
],
credentials: true,
}))
Railway 무료 한도
Railway는 매달 $5 크레딧을 무료로 제공한다. 소규모 서버는 이 범위 안에서 운영 가능하다. 트래픽이 늘면 유료 플랜으로 전환해야 한다.
공통: 배포 후 자주 겪는 문제
API 호출이 안 된다
환경 변수를 추가했는데 반영이 안 됐다면 Redeploy를 한 번 해야 한다. 환경 변수는 배포 시점에 주입되기 때문이다.
CORS 오류
브라우저 콘솔에 CORS policy 에러가 뜨면 백엔드 origin에 프론트엔드 URL이 정확히 들어가 있는지 확인한다. 끝에 / 슬래시 하나 차이로도 막힌다.
// ❌ 와일드카드 + credentials 조합은 동작하지 않는다
origin: '*', credentials: true
// ✅ 도메인을 명시해야 한다
origin: ['http://localhost:5173', process.env.CORS_ORIGIN]
MongoDB 연결 실패
MongooseServerSelectionError: connection timed outNetwork Access에서 0.0.0.0/0이 등록되어 있는지 확인한다. 등록 후 수 분이 지나야 적용된다. connection string의 <password>에 특수문자(@, # 등)가 있다면 URL 인코딩(%40, %23)이 필요하다.
배포 후 체크리스트
□ 프론트엔드 URL 접속 → 정상 로드 확인
□ 회원가입 → Atlas Data Explorer에서 DB 저장 확인
□ 로그인 → JWT 토큰 발급 확인
□ 인증이 필요한 API 호출 → 정상 응답 확인
□ 브라우저 콘솔 CORS 에러 없는지 확인
□ 모바일에서 접속 확인정리
| 상황 | 추천 |
|---|---|
| 간단한 REST API, 빠르게 배포 | Vercel Serverless |
| Express 코드 그대로 유지, WebSocket 필요 | Railway |
| 콜드 스타트 허용 불가 | Railway |
| 완전 무료 유지 | Vercel Serverless |
두 방법 모두 GitHub push 시 자동 재배포된다. 로컬에서 개발하고 push하면 배포까지 자동으로 처리된다.
'개발 팁' 카테고리의 다른 글
| JWT 인증 구현 — Node.js, Python, Go 비교 (0) | 2026.02.25 |
|---|---|
| npm 패키지 배포 전 체크리스트 — npm pack 활용법 (0) | 2026.02.25 |
| React + Vite 프로젝트 초기 세팅 — 매번 하는 것들 정리 (0) | 2026.02.25 |
| VSCode 필수 확장 프로그램 추천 - 2026년 실사용 기준 (0) | 2026.01.26 |