챕터 30: Vercel로 배포하기
서론
드디어 이 순간이 왔습니다! 30개 챕터의 긴 여정을 통해 우리는 HTML 기초부터 시작하여 Next.js의 고급 기능까지 모두 배웠습니다. 이제 마지막 단계인 배포만 남았습니다. Vercel은 Next.js를 만든 회사가 제공하는 호스팅 플랫폼으로, Next.js 애플리케이션을 배포하기에 가장 최적화된 환경을 제공합니다.
이번 챕터에서는 Vercel 계정을 만들고, GitHub와 연동하여 자동 배포를 설정하며, 커스텀 도메인을 연결하는 과정까지 단계별로 진행하겠습니다. 몇 번의 클릭만으로 여러분의 웹사이트가 전 세계 어디에서나 접속 가능한 실제 서비스로 탄생하게 됩니다. 이 감동적인 순간을 함께 경험해보겠습니다!
본론
Vercel 계정 만들기
먼저 Vercel 계정을 생성해야 합니다. 브라우저에서 https://vercel.com 으로 접속하세요:
// app/deployment-guide/page.js
export default function DeploymentGuidePage() {
return (
<div className="min-h-screen bg-gray-50 p-8">
<div className="max-w-4xl mx-auto">
<h1 className="text-3xl font-bold mb-8">🚀 Vercel 배포 가이드</h1>
<div className="bg-white rounded-lg shadow-md p-6 mb-6">
<h2 className="text-xl font-semibold mb-4">1단계: Vercel 계정 생성</h2>
<div className="space-y-4">
<div className="flex items-start">
<span className="bg-blue-500 text-white rounded-full w-6 h-6 flex items-center justify-center mr-3 mt-1">1</span>
<div>
<p className="font-semibold">vercel.com 접속</p>
<p className="text-gray-600">브라우저에서 Vercel 공식 웹사이트로 이동합니다</p>
</div>
</div>
<div className="flex items-start">
<span className="bg-blue-500 text-white rounded-full w-6 h-6 flex items-center justify-center mr-3 mt-1">2</span>
<div>
<p className="font-semibold">Sign Up 클릭</p>
<p className="text-gray-600">오른쪽 상단의 Sign Up 버튼을 클릭합니다</p>
</div>
</div>
<div className="flex items-start">
<span className="bg-blue-500 text-white rounded-full w-6 h-6 flex items-center justify-center mr-3 mt-1">3</span>
<div>
<p className="font-semibold">GitHub로 계속하기</p>
<p className="text-gray-600">GitHub 계정으로 로그인하는 것을 권장합니다</p>
<p className="text-sm text-yellow-600 mt-1">💡 GitHub 계정이 없다면 먼저 GitHub에 가입하세요</p>
</div>
</div>
<div className="flex items-start">
<span className="bg-blue-500 text-white rounded-full w-6 h-6 flex items-center justify-center mr-3 mt-1">4</span>
<div>
<p className="font-semibold">권한 승인</p>
<p className="text-gray-600">Vercel이 GitHub 저장소에 접근할 수 있도록 권한을 부여합니다</p>
</div>
</div>
</div>
</div>
<div className="bg-blue-50 border border-blue-200 rounded-lg p-6">
<h3 className="font-semibold text-blue-800 mb-2">💡 왜 GitHub 연동이 중요한가요?</h3>
<ul className="space-y-2 text-gray-700">
<li>• 코드를 Push하면 자동으로 배포됩니다</li>
<li>• Pull Request마다 미리보기 URL이 생성됩니다</li>
<li>• 팀 협업이 쉬워집니다</li>
<li>• 버전 관리와 롤백이 간편합니다</li>
</ul>
</div>
</div>
</div>
)
}
GitHub 저장소 준비하기
배포하기 전에 코드를 GitHub에 업로드해야 합니다:
// app/github-setup/page.js
export default function GitHubSetupPage() {
return (
<div className="min-h-screen bg-gray-50 p-8">
<div className="max-w-4xl mx-auto">
<h1 className="text-3xl font-bold mb-8">📦 GitHub 저장소 설정</h1>
<div className="bg-white rounded-lg shadow-md p-6 mb-6">
<h2 className="text-xl font-semibold mb-4">새 저장소 만들기</h2>
<pre className="bg-gray-900 text-white p-4 rounded overflow-x-auto mb-4">
<code>{`# 1. Git 초기화 (이미 했다면 건너뛰기)
git init
# 2. 모든 파일 추가
git add .
# 3. 첫 커밋
git commit -m "Initial commit: My awesome Next.js app"
# 4. GitHub에서 새 저장소 생성 후
git remote add origin https://github.com/username/my-nextjs-app.git
# 5. 코드 푸시
git push -u origin main`}</code>
</pre>
<div className="bg-yellow-50 border border-yellow-200 rounded p-4">
<p className="text-yellow-800 font-semibold mb-2">⚠️ .gitignore 확인사항</p>
<pre className="bg-white p-3 rounded text-sm">
<code>{`# 반드시 포함되어야 할 항목들
node_modules/
.next/
.env.local
.env*.local
out/
.DS_Store
*.pem
.vercel`}</code>
</pre>
</div>
</div>
<div className="bg-white rounded-lg shadow-md p-6">
<h2 className="text-xl font-semibold mb-4">저장소 구조 확인</h2>
<div className="bg-gray-100 rounded p-4 font-mono text-sm">
<div className="mb-1">📁 my-nextjs-app/</div>
<div className="ml-4 mb-1">📁 app/</div>
<div className="ml-8 mb-1">📄 layout.js</div>
<div className="ml-8 mb-1">📄 page.js</div>
<div className="ml-4 mb-1">📁 public/</div>
<div className="ml-4 mb-1">📁 components/</div>
<div className="ml-4 mb-1">📄 package.json</div>
<div className="ml-4 mb-1">📄 next.config.js</div>
<div className="ml-4 mb-1">📄 .gitignore</div>
<div className="ml-4">📄 README.md</div>
</div>
</div>
</div>
</div>
)
}
Vercel에서 프로젝트 가져오기
이제 실제 배포를 시작합니다:
// app/import-project/page.js
'use client'
import { useState } from 'react'
export default function ImportProjectPage() {
const [step, setStep] = useState(1)
return (
<div className="min-h-screen bg-gray-50 p-8">
<div className="max-w-4xl mx-auto">
<h1 className="text-3xl font-bold mb-8">🎯 프로젝트 가져오기</h1>
{/* 진행 표시기 */}
<div className="flex items-center justify-between mb-8">
{[1, 2, 3, 4].map(num => (
<div key={num} className="flex items-center">
<button
onClick={() => setStep(num)}
className={`w-10 h-10 rounded-full flex items-center justify-center font-bold ${
step >= num
? 'bg-blue-500 text-white'
: 'bg-gray-300 text-gray-600'
}`}
>
{num}
</button>
{num < 4 && (
<div className={`w-20 h-1 ${
step > num ? 'bg-blue-500' : 'bg-gray-300'
}`} />
)}
</div>
))}
</div>
{/* 단계별 내용 */}
{step === 1 && (
<div className="bg-white rounded-lg shadow-md p-6">
<h2 className="text-xl font-semibold mb-4">Step 1: New Project 클릭</h2>
<div className="bg-gray-100 rounded p-4 mb-4">
<p className="mb-2">Vercel 대시보드에서:</p>
<ol className="list-decimal list-inside space-y-2 ml-4">
<li>"Add New..." 버튼 클릭</li>
<li>"Project" 선택</li>
<li>Import Git Repository 페이지로 이동</li>
</ol>
</div>
<img
src="/api/placeholder/600/300"
alt="Vercel Dashboard"
className="w-full rounded border"
/>
<button
onClick={() => setStep(2)}
className="mt-4 bg-blue-500 text-white px-6 py-2 rounded hover:bg-blue-600"
>
다음 단계 →
</button>
</div>
)}
{step === 2 && (
<div className="bg-white rounded-lg shadow-md p-6">
<h2 className="text-xl font-semibold mb-4">Step 2: GitHub 저장소 선택</h2>
<div className="space-y-4">
<div className="border rounded p-4 hover:bg-gray-50 cursor-pointer">
<div className="flex justify-between items-center">
<div>
<p className="font-semibold">my-nextjs-app</p>
<p className="text-sm text-gray-600">github.com/username/my-nextjs-app</p>
</div>
<button className="bg-gray-900 text-white px-4 py-1 rounded">
Import
</button>
</div>
</div>
<div className="bg-blue-50 border border-blue-200 rounded p-4">
<p className="text-blue-800">
💡 저장소가 보이지 않는다면 "Adjust GitHub App Permissions"를 클릭하여
권한을 추가하세요
</p>
</div>
</div>
<div className="flex space-x-4 mt-4">
<button
onClick={() => setStep(1)}
className="bg-gray-300 text-gray-700 px-6 py-2 rounded hover:bg-gray-400"
>
← 이전
</button>
<button
onClick={() => setStep(3)}
className="bg-blue-500 text-white px-6 py-2 rounded hover:bg-blue-600"
>
다음 →
</button>
</div>
</div>
)}
{step === 3 && (
<div className="bg-white rounded-lg shadow-md p-6">
<h2 className="text-xl font-semibold mb-4">Step 3: 프로젝트 설정</h2>
<div className="space-y-6">
<div>
<label className="block font-semibold mb-2">프로젝트 이름</label>
<input
type="text"
value="my-nextjs-app"
className="w-full border rounded px-3 py-2"
/>
<p className="text-sm text-gray-600 mt-1">
이 이름이 URL의 일부가 됩니다: my-nextjs-app.vercel.app
</p>
</div>
<div>
<label className="block font-semibold mb-2">Framework Preset</label>
<select className="w-full border rounded px-3 py-2">
<option>Next.js (자동 감지됨)</option>
</select>
</div>
<div>
<label className="block font-semibold mb-2">Root Directory</label>
<input
type="text"
value="./"
className="w-full border rounded px-3 py-2"
/>
</div>
<div>
<h3 className="font-semibold mb-2">Build 설정 (자동 설정됨)</h3>
<div className="bg-gray-100 rounded p-3 font-mono text-sm">
<p>Build Command: next build</p>
<p>Output Directory: .next</p>
<p>Install Command: npm install</p>
</div>
</div>
</div>
<div className="flex space-x-4 mt-6">
<button
onClick={() => setStep(2)}
className="bg-gray-300 text-gray-700 px-6 py-2 rounded hover:bg-gray-400"
>
← 이전
</button>
<button
onClick={() => setStep(4)}
className="bg-blue-500 text-white px-6 py-2 rounded hover:bg-blue-600"
>
다음 →
</button>
</div>
</div>
)}
{step === 4 && (
<div className="bg-white rounded-lg shadow-md p-6">
<h2 className="text-xl font-semibold mb-4">Step 4: 환경 변수 설정</h2>
<div className="space-y-4">
<p className="text-gray-600">
프로덕션 환경에서 사용할 환경 변수를 설정합니다
</p>
<div className="border rounded p-4">
<div className="grid grid-cols-2 gap-4 mb-3">
<input
type="text"
placeholder="Key (예: DATABASE_URL)"
className="border rounded px-3 py-2"
/>
<input
type="text"
placeholder="Value"
className="border rounded px-3 py-2"
/>
</div>
<button className="text-blue-500 hover:text-blue-600">
+ Add another
</button>
</div>
<div className="bg-yellow-50 border border-yellow-200 rounded p-4">
<p className="text-yellow-800 font-semibold mb-2">
⚠️ 중요한 보안 사항
</p>
<ul className="text-sm space-y-1">
<li>• API 키와 비밀번호는 여기에 안전하게 저장됩니다</li>
<li>• NEXT_PUBLIC_ 접두사가 있는 변수만 클라이언트에 노출됩니다</li>
<li>• 값은 암호화되어 저장됩니다</li>
</ul>
</div>
<div className="bg-green-50 border border-green-200 rounded p-4">
<p className="text-green-800 font-semibold mb-2">
✅ 배포 준비 완료!
</p>
<p className="text-gray-700">
Deploy 버튼을 클릭하면 배포가 시작됩니다
</p>
</div>
<button className="w-full bg-green-500 text-white py-3 rounded-lg font-semibold hover:bg-green-600">
🚀 Deploy
</button>
</div>
</div>
)}
</div>
</div>
)
}
배포 과정 모니터링
// app/deployment-process/page.js
'use client'
import { useState, useEffect } from 'react'
export default function DeploymentProcessPage() {
const [deploymentStage, setDeploymentStage] = useState(0)
const [logs, setLogs] = useState([])
const stages = [
{ name: 'Cloning repository', duration: 2000 },
{ name: 'Installing dependencies', duration: 3000 },
{ name: 'Building application', duration: 4000 },
{ name: 'Generating static pages', duration: 2000 },
{ name: 'Deploying to edge network', duration: 2000 },
{ name: 'Running checks', duration: 1000 },
{ name: 'Deployment complete! 🎉', duration: 0 }
]
useEffect(() => {
if (deploymentStage < stages.length - 1) {
const timer = setTimeout(() => {
setDeploymentStage(prev => prev + 1)
setLogs(prev => [...prev, `✓ ${stages[deploymentStage].name}`])
}, stages[deploymentStage].duration)
return () => clearTimeout(timer)
}
}, [deploymentStage])
const progress = ((deploymentStage + 1) / stages.length) * 100
return (
<div className="min-h-screen bg-gray-50 p-8">
<div className="max-w-4xl mx-auto">
<h1 className="text-3xl font-bold mb-8">🔄 배포 진행 중...</h1>
<div className="bg-white rounded-lg shadow-md p-6 mb-6">
<div className="mb-4">
<div className="flex justify-between mb-2">
<span className="text-gray-600">진행률</span>
<span className="font-semibold">{Math.round(progress)}%</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-3">
<div
className="bg-gradient-to-r from-blue-500 to-green-500 h-3 rounded-full transition-all duration-500"
style={{ width: `${progress}%` }}
/>
</div>
</div>
<div className="mb-6">
<h2 className="font-semibold mb-3">현재 단계</h2>
<p className="text-lg text-blue-600">
{stages[deploymentStage].name}
</p>
</div>
<div className="bg-gray-900 rounded p-4 h-64 overflow-y-auto">
<div className="font-mono text-sm text-green-400">
{logs.map((log, index) => (
<div key={index} className="mb-1">
{log}
</div>
))}
{deploymentStage < stages.length - 1 && (
<div className="text-yellow-400">
⏳ {stages[deploymentStage].name}...
</div>
)}
</div>
</div>
</div>
{deploymentStage === stages.length - 1 && (
<div className="bg-green-50 border border-green-200 rounded-lg p-6">
<h2 className="text-2xl font-bold text-green-800 mb-4">
🎉 축하합니다! 배포가 완료되었습니다!
</h2>
<div className="space-y-3">
<div className="flex items-center justify-between bg-white rounded p-3">
<span className="font-semibold">프로덕션 URL:</span>
<a
href="https://my-nextjs-app.vercel.app"
className="text-blue-500 hover:underline"
target="_blank"
rel="noopener noreferrer"
>
https://my-nextjs-app.vercel.app
</a>
</div>
<div className="flex items-center justify-between bg-white rounded p-3">
<span className="font-semibold">배포 시간:</span>
<span>14초</span>
</div>
<div className="flex items-center justify-between bg-white rounded p-3">
<span className="font-semibold">상태:</span>
<span className="text-green-600 font-semibold">✓ Ready</span>
</div>
</div>
<div className="mt-6 flex space-x-4">
<button className="bg-blue-500 text-white px-6 py-2 rounded hover:bg-blue-600">
사이트 방문하기
</button>
<button className="bg-gray-300 text-gray-700 px-6 py-2 rounded hover:bg-gray-400">
대시보드로 이동
</button>
</div>
</div>
)}
</div>
</div>
)
}
커스텀 도메인 연결하기
// app/custom-domain/page.js
export default function CustomDomainPage() {
return (
<div className="min-h-screen bg-gray-50 p-8">
<div className="max-w-4xl mx-auto">
<h1 className="text-3xl font-bold mb-8">🌐 커스텀 도메인 연결</h1>
<div className="bg-white rounded-lg shadow-md p-6 mb-6">
<h2 className="text-xl font-semibold mb-4">도메인 추가하기</h2>
<div className="space-y-4">
<div>
<label className="block font-semibold mb-2">도메인 입력</label>
<div className="flex space-x-2">
<input
type="text"
placeholder="www.yourdomain.com"
className="flex-1 border rounded px-3 py-2"
/>
<button className="bg-blue-500 text-white px-6 py-2 rounded hover:bg-blue-600">
Add
</button>
</div>
</div>
<div className="bg-gray-100 rounded p-4">
<p className="font-semibold mb-2">권장 설정:</p>
<ul className="space-y-1 text-sm">
<li>✓ www.yourdomain.com (www 버전)</li>
<li>✓ yourdomain.com (루트 도메인)</li>
</ul>
</div>
</div>
</div>
<div className="bg-white rounded-lg shadow-md p-6 mb-6">
<h2 className="text-xl font-semibold mb-4">DNS 설정</h2>
<div className="space-y-4">
<div className="border rounded p-4">
<h3 className="font-semibold mb-2">A 레코드 (루트 도메인용)</h3>
<div className="bg-gray-100 rounded p-3 font-mono text-sm">
<p>Type: A</p>
<p>Name: @</p>
<p>Value: 76.76.21.21</p>
</div>
</div>
<div className="border rounded p-4">
<h3 className="font-semibold mb-2">CNAME 레코드 (www용)</h3>
<div className="bg-gray-100 rounded p-3 font-mono text-sm">
<p>Type: CNAME</p>
<p>Name: www</p>
<p>Value: cname.vercel-dns.com</p>
</div>
</div>
<div className="bg-blue-50 border border-blue-200 rounded p-4">
<p className="text-blue-800">
💡 DNS 변경사항이 반영되는데 최대 48시간이 걸릴 수 있습니다.
보통은 몇 분 내에 완료됩니다.
</p>
</div>
</div>
</div>
<div className="bg-white rounded-lg shadow-md p-6">
<h2 className="text-xl font-semibold mb-4">SSL 인증서</h2>
<div className="bg-green-50 border border-green-200 rounded p-4">
<div className="flex items-center mb-2">
<span className="text-2xl mr-3">🔒</span>
<div>
<p className="font-semibold text-green-800">자동 SSL 인증서</p>
<p className="text-gray-700">
Vercel이 Let's Encrypt를 통해 무료 SSL 인증서를 자동으로 발급하고 갱신합니다
</p>
</div>
</div>
</div>
</div>
</div>
</div>
)
}
배포 후 관리
// app/post-deployment/page.js
export default function PostDeploymentPage() {
return (
<div className="min-h-screen bg-gray-50 p-8">
<div className="max-w-6xl mx-auto">
<h1 className="text-3xl font-bold mb-8">📊 배포 후 관리</h1>
<div className="grid md:grid-cols-2 gap-6">
<div className="bg-white rounded-lg shadow-md p-6">
<h2 className="text-xl font-semibold mb-4">🔄 자동 배포</h2>
<div className="space-y-3">
<div className="border-l-4 border-green-500 pl-4">
<p className="font-semibold">main 브랜치</p>
<p className="text-sm text-gray-600">
Push할 때마다 프로덕션에 자동 배포
</p>
</div>
<div className="border-l-4 border-blue-500 pl-4">
<p className="font-semibold">Pull Request</p>
<p className="text-sm text-gray-600">
각 PR마다 미리보기 URL 생성
</p>
</div>
<div className="border-l-4 border-purple-500 pl-4">
<p className="font-semibold">다른 브랜치</p>
<p className="text-sm text-gray-600">
개발/스테이징 환경 자동 구성
</p>
</div>
</div>
</div>
<div className="bg-white rounded-lg shadow-md p-6">
<h2 className="text-xl font-semibold mb-4">📈 분석 도구</h2>
<div className="space-y-3">
<div className="flex justify-between items-center p-3 bg-gray-50 rounded">
<span>Web Vitals</span>
<span className="text-green-600 font-semibold">95/100</span>
</div>
<div className="flex justify-between items-center p-3 bg-gray-50 rounded">
<span>일일 방문자</span>
<span className="font-semibold">1,234</span>
</div>
<div className="flex justify-between items-center p-3 bg-gray-50 rounded">
<span>평균 로딩 시간</span>
<span className="font-semibold">0.8초</span>
</div>
<button className="w-full bg-blue-500 text-white py-2 rounded hover:bg-blue-600">
Vercel Analytics 활성화
</button>
</div>
</div>
<div className="bg-white rounded-lg shadow-md p-6">
<h2 className="text-xl font-semibold mb-4">🔧 환경 변수 관리</h2>
<div className="space-y-3">
<p className="text-gray-600">
Settings → Environment Variables에서 관리
</p>
<div className="bg-gray-100 rounded p-3">
<p className="font-mono text-sm mb-1">DATABASE_URL</p>
<p className="font-mono text-sm mb-1">NEXT_PUBLIC_API_URL</p>
<p className="font-mono text-sm">ANALYTICS_ID</p>
</div>
<p className="text-sm text-yellow-600">
⚠️ 변경 후 재배포가 필요합니다
</p>
</div>
</div>
<div className="bg-white rounded-lg shadow-md p-6">
<h2 className="text-xl font-semibold mb-4">🔙 롤백</h2>
<div className="space-y-3">
<p className="text-gray-600">
문제가 발생하면 이전 버전으로 즉시 롤백 가능
</p>
<div className="space-y-2">
<div className="flex justify-between items-center p-2 border rounded">
<span className="text-sm">v23 (현재)</span>
<span className="text-xs text-gray-500">2분 전</span>
</div>
<div className="flex justify-between items-center p-2 border rounded">
<span className="text-sm">v22</span>
<button className="text-blue-500 text-xs hover:underline">
롤백
</button>
</div>
<div className="flex justify-between items-center p-2 border rounded">
<span className="text-sm">v21</span>
<button className="text-blue-500 text-xs hover:underline">
롤백
</button>
</div>
</div>
</div>
</div>
</div>
<div className="mt-8 bg-gradient-to-r from-purple-500 to-pink-500 text-white rounded-lg p-8">
<h2 className="text-2xl font-bold mb-4">🎊 축하합니다!</h2>
<p className="text-lg mb-4">
30개 챕터의 긴 여정을 완주하셨습니다! 이제 여러분은 진정한 Next.js 개발자입니다.
</p>
<p className="mb-6">
HTML 기초부터 시작하여 컴포넌트, 라우팅, API, 그리고 배포까지
웹 개발의 전체 과정을 마스터하셨습니다.
</p>
<div className="bg-white bg-opacity-20 rounded p-4">
<p className="font-semibold mb-2">🚀 다음 단계 추천:</p>
<ul className="space-y-1">
<li>• 실제 프로젝트 만들어보기</li>
<li>• TypeScript 학습하기</li>
<li>• 데이터베이스 연동 (Prisma, MongoDB)</li>
<li>• 인증 시스템 구현 (NextAuth.js)</li>
<li>• 테스트 코드 작성 (Jest, Cypress)</li>
</ul>
</div>
</div>
</div>
</div>
)
}
결론
드디어 해냈습니다! 여러분의 웹사이트가 이제 전 세계 어디에서나 접속 가능한 실제 서비스가 되었습니다. Vercel을 통한 배포는 놀라울 정도로 간단하면서도 강력합니다. Git에 코드를 푸시하기만 하면 자동으로 빌드되고 배포되는 마법 같은 과정을 경험하셨을 것입니다.
이번 챕터에서 배운 내용을 정리하면:
- Vercel 계정 생성과 GitHub 연동
- 프로젝트 가져오기와 설정
- 환경 변수 관리
- 커스텀 도메인 연결
- 배포 후 관리와 모니터링
30개 챕터의 긴 여정이 끝났습니다. 처음 HTML의 div 태그를 배울 때를 기억하시나요? 그때는 단순한 박스를 만드는 것도 신기했을 텐데, 이제는 완전한 웹 애플리케이션을 만들고 배포까지 할 수 있게 되었습니다. 이것은 정말 놀라운 성장입니다.
하지만 이것은 끝이 아니라 새로운 시작입니다. 웹 개발의 세계는 끊임없이 발전하고 있으며, 배울 것들이 무궁무진합니다. 이제 여러분은 탄탄한 기초를 갖추었으니, 더 복잡하고 흥미로운 프로젝트에 도전할 준비가 되었습니다.
마지막으로, 여기까지 오신 여러분의 노력과 인내에 진심으로 박수를 보냅니다. 웹 개발자로서의 여정을 계속 이어가시길 바라며, 언제나 호기심을 잃지 않고 새로운 것을 배우는 즐거움을 느끼시길 바랍니다.
여러분의 성공적인 웹 개발 여정을 응원합니다! 🚀