Next.js와 FastAPI를 연동하여 강력한 풀스택 개발 환경을 구축하는 방법을 단계별로 안내합니다. 프론트엔드와 백엔드를 효율적으로 통합하고 배포하는 실전 노하우를 확인하세요.
웹 개발 프로젝트를 시작할 때, 프론트엔드와 백엔드를 어떤 기술 스택으로 구성할지 고민하는 개발자가 많습니다. 특히, 단일 팀 또는 개인이 전체 애플리케이션의 개발과 유지보수를 담당하는 풀스택 개발 환경에서는 각 기술 스택의 강점을 최대한 활용하면서도 효율적인 연동이 필수적입니다. 프론트엔드는 사용자 경험을 최적화하고, 백엔드는 안정적이고 고성능의 데이터 처리를 제공해야 하죠. 하지만 이 두 가지를 유기적으로 연결하는 과정에서 수많은 시행착오와 문제에 직면하기도 합니다.
이런 고민을 해결하기 위해 Next.js와 FastAPI의 조합은 매우 매력적인 대안으로 떠오르고 있습니다. Next.js는 React 기반의 강력한 프레임워크로, 뛰어난 개발자 경험과 성능 최적화를 제공하며, FastAPI는 파이썬 생태계에서 고성능 비동기 API 서버를 구축하기에 최적화된 프레임워크입니다. 이 둘을 효과적으로 연동하면 생산성과 확장성 모두를 잡을 수 있는 풀스택 개발 환경을 구축할 수 있습니다.
이 가이드에서는 Next.js와 FastAPI를 활용하여 풀스택 개발 환경을 구축하는 실질적인 방법을 단계별로 안내합니다. 각 단계마다 발생할 수 있는 문제점과 그 해결책을 함께 제시하여, 여러분이 프로젝트를 성공적으로 완성할 수 있도록 돕겠습니다.
📑 목차
- 왜 Next.js와 FastAPI인가? 풀스택 개발의 핵심 조합
- Next.js의 강점: 현대적인 프론트엔드 개발의 표준
- FastAPI의 매력: 고성능 비동기 백엔드 프레임워크
- 개발 환경 설정: 프로젝트 초기화부터 기본 구성까지
- Next.js 프로젝트 생성 및 기본 설정
- FastAPI 프로젝트 생성 및 가상 환경 설정
- Next.js와 FastAPI 연동: API 통신 구현의 모든 것
- FastAPI 백엔드 API 설계 및 구현
- Next.js 프론트엔드에서 API 호출
- 데이터베이스 연동 및 관리 (선택 사항이지만 중요)
- FastAPI와 데이터베이스 ORM 연동 (SQLAlchemy, Alembic)
- 데이터베이스 환경 변수 관리
- 효율적인 개발 워크플로우 구축: 개발 서버와 배포 전략
- 개발 서버 동시 실행 및 Proxy 설정 (Next.js next.config.js)
- 컨테이너 기반 배포 고려 (Docker)
- 흔히 겪는 문제와 해결책: 트러블슈팅 가이드
- CORS 문제 해결
- 환경 변수 관리 오류
- 배포 시 발생 가능한 문제 (정적 파일, 경로 설정 등)
- 결론: Next.js와 FastAPI로 완성하는 강력한 풀스택 개발
Image by Boskampi on Pixabay
왜 Next.js와 FastAPI인가? 풀스택 개발의 핵심 조합
수많은 프론트엔드와 백엔드 프레임워크 중에서 왜 Next.js와 FastAPI가 풀스택 개발에 이상적인 조합으로 평가받을까요? 각 프레임워크의 고유한 강점과 시너지를 이해하는 것이 중요합니다.
Next.js의 강점: 현대적인 프론트엔드 개발의 표준
Next.js는 React 기반의 웹 애플리케이션 프레임워크로, 개발자가 직면하는 다양한 성능 및 개발 편의성 문제를 해결해 줍니다. 대표적인 강점은 다음과 같습니다:
- 서버 사이드 렌더링 (SSR) 및 정적 사이트 생성 (SSG): 웹 페이지의 초기 로딩 속도를 향상시키고 SEO(검색 엔진 최적화)에 유리합니다. 사용자의 요청에 따라 서버에서 페이지를 미리 렌더링하거나, 빌드 시점에 HTML 파일을 생성하여 제공할 수 있습니다.
- 파일 시스템 기반 라우팅: 직관적인 파일 구조를 통해 페이지 라우팅을 관리할 수 있어 개발 생산성이 높습니다. 복잡한 라우팅 설정을 줄여줍니다.
- API Routes: Next.js 프로젝트 내에서 서버리스 함수처럼 동작하는 백엔드 API를 쉽게 구현할 수 있습니다. 간단한 API 연동이나 특정 서버 로직이 필요할 때 유용합니다.
- 최적화된 이미지 및 폰트 관리:
next/image컴포넌트를 통해 이미지 최적화를 자동으로 처리하고, 폰트도 효율적으로 로드하여 성능을 향상시킵니다. - 풍부한 생태계와 커뮤니티: React의 광범위한 생태계를 그대로 활용할 수 있으며, 활발한 커뮤니티를 통해 문제 해결 및 정보 공유가 용이합니다.
FastAPI의 매력: 고성능 비동기 백엔드 프레임워크
FastAPI는 파이썬 기반의 웹 프레임워크로, 현대적인 API 서버 개발에 최적화되어 있습니다. 파이썬의 강력함과 효율성을 결합하여 다음과 같은 장점을 제공합니다:
- 높은 성능: Starlette과 Pydantic을 기반으로 하여 Node.js나 Go와 견줄 만한 높은 성능을 자랑합니다. 비동기 처리를 기본으로 지원하여 I/O 바운드 작업에 매우 효율적입니다.
- 자동 문서화: OpenAPI(Swagger UI) 및 ReDoc 기반의 API 문서를 자동으로 생성합니다. 개발 및 협업 시 API 명세 관리의 번거로움을 크게 줄여줍니다.
- 데이터 유효성 검사 (Pydantic): Pydantic을 활용하여 요청 및 응답 데이터의 유효성을 자동으로 검사합니다. 런타임 오류를 줄이고 코드의 안정성을 높이는 데 기여합니다.
- 간결하고 직관적인 문법: 파이썬의 타입 힌트를 적극 활용하여 코드 가독성과 생산성을 높입니다. 적은 코드로도 강력한 기능을 구현할 수 있습니다.
- 의존성 주입 시스템: 테스트 용이성을 높이고 코드 재사용성을 증진시키는 강력한 의존성 주입(Dependency Injection) 시스템을 제공합니다.
이 두 프레임워크를 함께 사용하면, Next.js의 뛰어난 프론트엔드 성능과 개발 경험, 그리고 FastAPI의 고성능 및 안정적인 백엔드 API 구축 능력을 동시에 활용하여 시너지를 극대화할 수 있습니다.
| 특징 | Next.js | FastAPI |
|---|---|---|
| 역할 | 프론트엔드 (클라이언트 측) | 백엔드 (서버 측) |
| 기반 언어/기술 | JavaScript, React | Python |
| 주요 강점 | SSR/SSG, 파일 시스템 라우팅, API Routes, 이미지 최적화, SEO | 고성능 비동기 처리, 자동 API 문서화, Pydantic 데이터 유효성 검사, 의존성 주입 |
| 개발 생산성 | 높음 (직관적인 구조, React 생태계 활용) | 높음 (간결한 문법, 자동화된 기능) |
개발 환경 설정: 프로젝트 초기화부터 기본 구성까지
Next.js와 FastAPI 프로젝트를 생성하고 기본적인 개발 환경을 설정하는 과정은 풀스택 개발의 첫 단추입니다. 올바른 초기 설정은 향후 발생할 수 있는 많은 문제를 예방하는 데 도움이 됩니다.
Next.js 프로젝트 생성 및 기본 설정
먼저 Next.js 프로젝트를 생성합니다. 터미널을 열고 다음 명령어를 실행합니다.
npx create-next-app@latest nextjs-frontend --typescript --tailwind --eslint
이 명령어는 nextjs-frontend라는 이름의 Next.js 프로젝트를 생성하며, TypeScript, Tailwind CSS, ESLint 설정을 포함합니다. 프롬프트에 따라 원하는 설정을 선택하세요. 특히 App Router 사용 여부는 프로젝트 구조에 큰 영향을 미치므로 신중하게 선택하는 것이 좋습니다. 일반적으로는 최신 버전의 App Router를 사용하는 것을 권장합니다.
프로젝트 생성 후, 개발 서버를 실행하여 정상적으로 작동하는지 확인합니다.
cd nextjs-frontend
npm run dev
브라우저에서 http://localhost:3000에 접속하여 Next.js 시작 페이지가 보이는지 확인합니다.
환경 변수 설정: 백엔드 API 주소와 같은 중요한 정보는 .env.local 파일에 저장하는 것이 좋습니다. 이 파일은 Git 추적에서 제외되므로 민감한 정보를 안전하게 관리할 수 있습니다.
# nextjs-frontend/.env.local
NEXT_PUBLIC_API_URL=http://localhost:8000/api
NEXT_PUBLIC_ 접두사가 붙은 변수는 클라이언트 측 코드에서도 접근할 수 있습니다. 백엔드 API의 기본 경로를 이 변수에 저장하여 프론트엔드에서 일관되게 사용하도록 합니다.
FastAPI 프로젝트 생성 및 가상 환경 설정
다음으로 FastAPI 프로젝트를 설정합니다. Next.js 프로젝트와는 별도의 디렉토리에서 작업하는 것이 일반적입니다.
먼저 Python 가상 환경을 생성하고 활성화합니다. 이는 프로젝트별로 의존성을 분리하여 관리하는 데 필수적입니다.
mkdir fastapi-backend
cd fastapi-backend
python -m venv venv
source venv/bin/activate # macOS/Linux
# venv\Scripts\activate # Windows
가상 환경이 활성화되면, FastAPI와 Uvicorn (ASGI 서버)을 설치합니다.
pip install fastapi uvicorn[standard]
이제 간단한 FastAPI 애플리케이션을 생성합니다. main.py 파일을 생성하고 다음 코드를 작성합니다.
# fastapi-backend/main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
# CORS 설정: Next.js 프론트엔드에서 백엔드 API를 호출할 수 있도록 허용합니다.
# 실제 배포 환경에서는 허용할 Origin을 명시적으로 지정하는 것이 보안상 더 좋습니다.
origins = [
"http://localhost:3000", # Next.js 개발 서버 주소
# "https://your-frontend-domain.com", # 배포 환경 프론트엔드 주소
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/")
async def read_root():
return {"message": "Hello from FastAPI backend!"}
@app.get("/api/items")
async def read_items():
return [
{"id": 1, "name": "Item 1", "description": "This is item 1"},
{"id": 2, "name": "Item 2", "description": "This is item 2"},
]
FastAPI 서버를 실행합니다.
uvicorn main:app --reload
--reload 옵션은 코드 변경 시 서버를 자동으로 다시 로드하여 개발 편의성을 높여줍니다. 브라우저에서 http://localhost:8000에 접속하여 "Hello from FastAPI backend!" 메시지가 뜨는지 확인합니다. 또한 http://localhost:8000/docs에 접속하여 FastAPI의 자동 생성된 API 문서(Swagger UI)를 확인할 수 있습니다.
CORS (Cross-Origin Resource Sharing) 설정은 Next.js 프론트엔드가 FastAPI 백엔드에 요청을 보낼 때 발생하는 보안 문제를 해결하기 위해 필수적입니다. 위 코드에서 CORSMiddleware를 사용하여 http://localhost:3000からの 요청을 허용했습니다. 실제 프로덕션 환경에서는 allow_origins에 배포된 프론트엔드 도메인만 명시적으로 추가하는 것이 중요합니다.
Next.js와 FastAPI 연동: API 통신 구현의 모든 것
프론트엔드와 백엔드 환경 설정이 완료되었다면, 이제 Next.js에서 FastAPI API를 호출하여 데이터를 주고받는 방법을 살펴보겠습니다. 이 과정은 풀스택 애플리케이션의 핵심적인 부분입니다.
FastAPI 백엔드 API 설계 및 구현
실제 애플리케이션에서 사용될 API를 FastAPI로 구현해 보겠습니다. 간단한 할 일(Todo) 목록 관리 API를 예시로 들어보죠. main.py 파일을 다음과 같이 수정합니다.
# fastapi-backend/main.py
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import List, Dict, Union
app = FastAPI()
origins = [
"http://localhost:3000",
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Pydantic 모델 정의: 데이터 유효성 검사 및 문서화에 사용됩니다.
class TodoItem(BaseModel):
id: int
title: str
completed: bool = False
# 임시 데이터베이스 (실제 프로젝트에서는 데이터베이스와 연동)
todos_db: Dict[int, TodoItem] = {
1: TodoItem(id=1, title="FastAPI 백엔드 구축", completed=True),
2: TodoItem(id=2, title="Next.js 프론트엔드 연동", completed=False),
3: TodoItem(id=3, title="풀스택 개발 가이드 작성", completed=False),
}
next_id = 4 # 다음 할 일 항목의 ID
@app.get("/api/todos", response_model=List[TodoItem])
async def get_todos():
"""모든 할 일 항목을 조회합니다."""
return list(todos_db.values())
@app.post("/api/todos", response_model=TodoItem, status_code=201)
async def create_todo(todo: TodoItem):
"""새로운 할 일 항목을 생성합니다."""
global next_id
if todo.id in todos_db:
raise HTTPException(status_code=400, detail="Todo with this ID already exists")
if todo.id is None: # 클라이언트가 ID를 보내지 않을 경우 서버에서 할당
todo.id = next_id
next_id += 1
todos_db[todo.id] = todo
return todo
@app.get("/api/todos/{todo_id}", response_model=TodoItem)
async def get_todo(todo_id: int):
"""특정 할 일 항목을 ID로 조회합니다."""
if todo_id not in todos_db:
raise HTTPException(status_code=404, detail="Todo not found")
return todos_db[todo_id]
@app.put("/api/todos/{todo_id}", response_model=TodoItem)
async def update_todo(todo_id: int, todo: TodoItem):
"""특정 할 일 항목을 업데이트합니다."""
if todo_id not in todos_db:
raise HTTPException(status_code=404, detail="Todo not found")
todos_db[todo_id] = todo
return todo
@app.delete("/api/todos/{todo_id}", status_code=204)
async def delete_todo(todo_id: int):
"""특정 할 일 항목을 삭제합니다."""
if todo_id not in todos_db:
raise HTTPException(status_code=404, detail="Todo not found")
del todos_db[todo_id]
return {"message": "Todo deleted successfully"} # 204 No Content는 보통 응답 본문이 없습니다.
이 코드는 /api/todos 경로에 대해 GET, POST, PUT, DELETE 메서드를 지원하는 RESTful API를 구현합니다. Pydantic TodoItem 모델을 사용하여 요청 및 응답 데이터의 구조와 타입을 명확히 정의합니다. 이는 자동 문서화와 데이터 유효성 검사에 큰 도움이 됩니다.
Next.js 프론트엔드에서 API 호출
이제 Next.js 프론트엔드에서 위에서 구현한 FastAPI API를 호출하여 데이터를 화면에 표시하고 상호작용하는 방법을 알아보겠습니다. axios 라이브러리를 사용하여 API 호출을 더 편리하게 처리할 수 있습니다. 먼저 axios를 설치합니다.
cd nextjs-frontend
npm install axios
다음으로 src/app/page.tsx (App Router 기준) 파일을 수정하여 할 일 목록을 표시하고 추가하는 기능을 구현합니다. 클라이언트 컴포넌트에서 API 호출을 수행해야 하므로 "use client" 지시자를 추가합니다.
// nextjs-frontend/src/app/page.tsx
"use client";
import { useEffect, useState } from 'react';
import axios from 'axios';
interface TodoItem {
id: number;
title: string;
completed: boolean;
}
export default function Home() {
const [todos, setTodos] = useState<TodoItem[]>([]);
const [newTodoTitle, setNewTodoTitle] = useState('');
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// 환경 변수에서 API URL 가져오기
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000/api';
useEffect(() => {
fetchTodos();
}, []);
const fetchTodos = async () => {
try {
setLoading(true);
setError(null);
const response = await axios.get(`${API_URL}/todos`);
setTodos(response.data);
} catch (err) {
console.error('Failed to fetch todos:', err);
setError('할 일 목록을 불러오는 데 실패했습니다.');
} finally {
setLoading(false);
}
};
const addTodo = async () => {
if (!newTodoTitle.trim()) return;
try {
const response = await axios.post(`${API_URL}/todos`, {
title: newTodoTitle,
completed: false,
});
setTodos((prevTodos) => [...prevTodos, response.data]);
setNewTodoTitle('');
} catch (err) {
console.error('Failed to add todo:', err);
setError('할 일을 추가하는 데 실패했습니다.');
}
};
const toggleTodoCompletion = async (id: number) => {
const todoToUpdate = todos.find(todo => todo.id === id);
if (!todoToUpdate) return;
try {
const updatedTodo = { ...todoToUpdate, completed: !todoToUpdate.completed };
const response = await axios.put(`${API_URL}/todos/${id}`, updatedTodo);
setTodos((prevTodos) =>
prevTodos.map((todo) => (todo.id === id ? response.data : todo))
);
} catch (err) {
console.error('Failed to update todo:', err);
setError('할 일을 업데이트하는 데 실패했습니다.');
}
};
const deleteTodo = async (id: number) => {
try {
await axios.delete(`${API_URL}/todos/${id}`);
setTodos((prevTodos) => prevTodos.filter((todo) => todo.id !== id));
} catch (err) {
console.error('Failed to delete todo:', err);
setError('할 일을 삭제하는 데 실패했습니다.');
}
};
if (loading) return <div className="container mx-auto p-4">로딩 중...</div>;
if (error) return <div className="container mx-auto p-4 text-red-500">오류: {error}</div>;
return (
<div className="container mx-auto p-4 max-w-lg">
<h1 className="text-3xl font-bold mb-6 text-center">Next.js & FastAPI Todo App</h1>
<div className="flex mb-4">
<input
type="text"
className="flex-grow p-2 border border-gray-300 rounded-l-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="새로운 할 일을 입력하세요"
value={newTodoTitle}
onChange={(e) => setNewTodoTitle(e.target.value)}
onKeyPress={(e) => {
if (e.key === 'Enter') addTodo();
}}
/>
<button
className="bg-blue-600 text-white px-4 py-2 rounded-r-md hover:bg-blue-700 transition-colors"
onClick={addTodo}
>
추가
</button>
</div>
<ul className="bg-white shadow-md rounded-lg divide-y divide-gray-200">
{todos.map((todo) => (
<li key={todo.id} className="flex items-center justify-between p-4">
<div className="flex items-center">
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodoCompletion(todo.id)}
className="form-checkbox h-5 w-5 text-blue-600 rounded mr-3"
/>
<span className={`text-lg ${todo.completed ? 'line-through text-gray-500' : 'text-gray-800'}`}>
{todo.title}
</span>
</div>
<button
className="bg-red-500 text-white px-3 py-1 rounded-md text-sm hover:bg-red-600 transition-colors"
onClick={() => deleteTodo(todo.id)}
>
삭제
</button>
</li>
))}
</ul>
{todos.length === 0 && !loading && !error && (
<p className="text-center text-gray-500 mt-4">할 일이 없습니다. 새로운 할 일을 추가해보세요!</p>
)}
</div>
);
}
이 코드에서는 useEffect 훅을 사용하여 컴포넌트가 마운트될 때 할 일 목록을 불러옵니다. axios를 사용하여 GET, POST, PUT, DELETE 요청을 FastAPI 백엔드로 보냅니다. 환경 변수 NEXT_PUBLIC_API_URL을 활용하여 백엔드 API의 기본 URL을 관리하므로, 개발 및 배포 환경에서 유연하게 대응할 수 있습니다. 각 API 호출에는 에러 처리 및 로딩 상태 관리가 포함되어 있어 사용자 경험을 개선합니다.
Image by jamesmarkosborne on Pixabay
데이터베이스 연동 및 관리 (선택 사항이지만 중요)
실제 애플리케이션에서는 임시 메모리 데이터 대신 영구적인 데이터베이스를 사용해야 합니다. FastAPI는 다양한 데이터베이스와 쉽게 연동할 수 있으며, 특히 파이썬 생태계의 SQLAlchemy는 강력한 ORM(Object-Relational Mapping) 기능을 제공합니다. 여기서는 PostgreSQL을 예시로 들어 FastAPI와 데이터베이스를 연동하는 방법을 간략히 소개합니다.
FastAPI와 데이터베이스 ORM 연동 (SQLAlchemy, Alembic)
데이터베이스 연동을 위해 psycopg2-binary (PostgreSQL 드라이버), SQLAlchemy, Alembic (마이그레이션 도구)를 설치합니다.
pip install psycopg2-binary sqlalchemy alembic
SQLAlchemy 모델 정의: models.py 파일을 생성하여 데이터베이스 테이블에 해당하는 파이썬 클래스를 정의합니다.
# fastapi-backend/models.py
from sqlalchemy import create_engine, Column, Integer, String, Boolean
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
# 데이터베이스 URL (환경 변수에서 불러오는 것이 일반적)
DATABASE_URL = "postgresql://user:password@localhost/dbname" # 실제 DB 정보로 변경
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
class DBTodo(Base):
__tablename__ = "todos" # 테이블 이름
id = Column(Integer, primary_key=True, index=True)
title = Column(String, index=True)
completed = Column(Boolean, default=False)
데이터베이스 세션 관리: main.py에서 데이터베이스 세션을 의존성 주입으로 관리합니다.
# fastapi-backend/main.py (일부 수정)
from sqlalchemy.orm import Session
from . import models, schemas # models.py와 schemas.py가 있다고 가정
# ... 생략 ...
# 데이터베이스 테이블 생성 (개발 환경에서만 사용)
models.Base.metadata.create_all(bind=models.engine)
# DB 세션 의존성
def get_db():
db = models.SessionLocal()
try:
yield db
finally:
db.close()
@app.post("/api/todos", response_model=schemas.TodoItem, status_code=201)
async def create_todo(todo: schemas.TodoCreate, db: Session = Depends(get_db)):
db_todo = models.DBTodo(title=todo.title, completed=todo.completed)
db.add(db_todo)
db.commit()
db.refresh(db_todo)
return db_todo
# ... 다른 CRUD 함수들도 db 세션과 연동하도록 수정 ...
이처럼 SQLAlchemy를 사용하면 객체 지향적으로 데이터베이스를 다룰 수 있으며, Alembic을 통해 데이터베이스 스키마 변경 사항을 버전 관리하고 마이그레이션할 수 있습니다. 이는 장기적인 프로젝트 유지보수와 팀 협업에 큰 이점을 제공합니다.
데이터베이스 환경 변수 관리
데이터베이스 연결 정보(호스트, 포트, 사용자명, 비밀번호 등)는 민감한 정보이므로 환경 변수로 관리해야 합니다. .env 파일에 정의하고, FastAPI 애플리케이션에서 python-dotenv 라이브러리를 사용하여 로드하는 것이 일반적입니다.
# fastapi-backend/.env
DATABASE_URL="postgresql://user:password@localhost/dbname"
그리고 main.py에서 환경 변수를 로드합니다.
# fastapi-backend/main.py (상단에 추가)
from dotenv import load_dotenv
import os
load_dotenv() # .env 파일에서 환경 변수를 로드합니다.
DATABASE_URL = os.getenv("DATABASE_URL")
if not DATABASE_URL:
raise ValueError("DATABASE_URL environment variable not set")
# ... models.py에서 DATABASE_URL을 이 변수로 사용하도록 수정 ...
이렇게 하면 데이터베이스 연결 정보를 코드에 직접 노출하지 않고 안전하게 관리할 수 있습니다.
효율적인 개발 워크플로우 구축: 개발 서버와 배포 전략
Next.js와 FastAPI를 연동하여 개발할 때는 두 서버를 동시에 실행하고, 배포 시에는 효율적으로 통합하는 전략이 필요합니다.
개발 서버 동시 실행 및 Proxy 설정 (Next.js next.config.js)
개발 중에는 Next.js 프론트엔드 서버(보통 3000번 포트)와 FastAPI 백엔드 서버(보통 8000번 포트)를 동시에 실행해야 합니다. 각각의 프로젝트 디렉토리에서 다음 명령어를 실행합니다.
# nextjs-frontend 디렉토리에서
npm run dev
# fastapi-backend 디렉토리에서
uvicorn main:app --reload
이때 프론트엔드에서 백엔드로 요청을 보낼 때 CORS 문제가 발생할 수 있습니다. 이미 FastAPI에서 CORSMiddleware를 설정했지만, Next.js의 rewrites 기능을 사용하여 프론트엔드 개발 서버에서 백엔드로 요청을 프록시하는 방법도 유용합니다. 이렇게 하면 프론트엔드 코드는 항상 동일한 도메인으로 요청을 보내는 것처럼 보이게 되어 개발 편의성이 향상됩니다.
next.config.js 파일을 다음과 같이 수정합니다.
// nextjs-frontend/next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
async rewrites() {
return [
{
source: '/api/:path*', // Next.js에서 /api로 시작하는 모든 요청
destination: 'http://localhost:8000/api/:path*', // FastAPI 백엔드로 프록시
},
];
},
};
module.exports = nextConfig;
이제 프론트엔드 코드에서는 /api/todos와 같이 상대 경로로 API를 호출할 수 있습니다. NEXT_PUBLIC_API_URL 환경 변수도 /api로 설정하면 됩니다.
# nextjs-frontend/.env.local
NEXT_PUBLIC_API_URL=/api
이 설정 덕분에 Next.js 개발 서버가 /api로 들어오는 요청을 자동으로 FastAPI 서버로 전달하여, 개발 시 CORS 문제를 우회하고 깔끔한 API 호출 경로를 유지할 수 있습니다.
컨테이너 기반 배포 고려 (Docker)
Docker는 Next.js와 FastAPI 애플리케이션을 안정적으로 빌드하고 배포하는 데 매우 효과적인 도구입니다. 각 애플리케이션을 독립적인 컨테이너로 패키징하고, Docker Compose를 사용하여 이 컨테이너들을 쉽게 오케스트레이션할 수 있습니다.
Next.js Dockerfile 예시:
# nextjs-frontend/Dockerfile
# 1단계: 빌드 환경 설정
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
COPY . .
ENV NEXT_PUBLIC_API_URL=http://localhost:8000/api # 배포 시에는 실제 백엔드 URL로 변경하거나 환경 변수로 주입
RUN npm run build
# 2단계: 프로덕션 환경 설정
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json
USER nextjs
EXPOSE 3000
CMD ["npm", "start"]
FastAPI Dockerfile 예시:
# fastapi-backend/Dockerfile
FROM python:3.10-slim-buster
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Docker Compose 예시 (monorepo 스타일):
두 프로젝트를 한 레포지토리에서 관리하는 경우 (monorepo) docker-compose.yml 파일을 루트 디렉토리에 생성하여 두 서비스를 한 번에 관리할 수 있습니다.
# docker-compose.yml
version: '3.8'
services:
frontend:
build:
context: ./nextjs-frontend
dockerfile: Dockerfile
ports:
- "3000:3000"
environment:
# Docker Compose 내부에서 서비스 이름으로 통신
NEXT_PUBLIC_API_URL: http://backend:8000/api
depends_on:
- backend
restart: always
backend:
build:
context: ./fastapi-backend
dockerfile: Dockerfile
ports:
- "8000:8000"
environment:
DATABASE_URL: postgresql://user:password@db:5432/dbname # 데이터베이스 서비스와 연동
restart: always
db:
image: postgres:13
environment:
POSTGRES_DB: dbname
POSTGRES_USER: user
POSTGRES_PASSWORD: password
volumes:
- db-data:/var/lib/postgresql/data
restart: always
volumes:
db-data:
이 docker-compose.yml 파일은 프론트엔드, 백엔드, 데이터베이스(PostgreSQL) 세 가지 서비스를 정의합니다. NEXT_PUBLIC_API_URL이 http://backend:8000/api로 설정되어 있는데, 이는 Docker Compose 네트워크 내에서 backend라는 서비스 이름으로 FastAPI 컨테이너에 접근하겠다는 의미입니다. docker-compose up -d 명령어로 모든 서비스를 한 번에 시작할 수 있습니다.
Image by fancycrave1 on Pixabay
흔히 겪는 문제와 해결책: 트러블슈팅 가이드
풀스택 개발 환경을 구축하고 운영하는 과정에서 몇 가지 흔한 문제에 직면할 수 있습니다. 이러한 문제들을 빠르게 진단하고 해결하는 방법을 아는 것은 매우 중요합니다.
CORS 문제 해결
문제 상황: Next.js 프론트엔드에서 FastAPI 백엔드로 API 요청을 보낼 때 브라우저 콘솔에 "Access to XMLHttpRequest at 'http://localhost:8000/api/...' from origin 'http://localhost:3000' has been blocked by CORS policy..."와 같은 오류 메시지가 나타납니다.
원인: 웹 브라우저는 보안상의 이유로 다른 출처(Origin, 즉 도메인, 포트, 프로토콜 중 하나라도 다른 경우)로의 HTTP 요청을 기본적으로 제한합니다. 프론트엔드(localhost:3000)와 백엔드(localhost:8000)의 포트가 다르기 때문에 이 문제가 발생합니다.
해결책:
- FastAPI에서
CORSMiddleware설정: 가장 일반적이고 권장되는 방법입니다. FastAPI 애플리케이션에CORSMiddleware를 추가하여 Next.js 프론트엔드 도메인からの 요청을 명시적으로 허용해야 합니다. 위 "FastAPI 프로젝트 생성" 섹션의 코드를 참조하세요.allow_origins에 Next.js 프론트엔드의 정확한 URL(예:"http://localhost:3000")을 추가하는 것이 핵심입니다. - Next.js
rewrites를 통한 Proxy 설정: 개발 환경에서만 유용하며, 프론트엔드 서버가 백엔드 서버로 요청을 프록시하도록 설정하여 브라우저 입장에서는 동일 출처 요청으로 보이게 만듭니다. 위 "개발 서버 동시 실행 및 Proxy 설정" 섹션의next.config.js설정을 참조하세요. 이 방법은 개발 중 CORS 문제를 우회하는 데 매우 편리합니다.
주의 사항: 프로덕션 환경에서는 allow_origins=["*"]와 같이 모든 출처를 허용하는 것은 보안상 매우 위험합니다. 반드시 실제 프론트엔드 도메인만 허용하도록 설정해야 합니다.
환경 변수 관리 오류
문제 상황: 환경 변수를 설정했는데 애플리케이션에서 제대로 읽지 못하거나, 특정 환경에서만 문제가 발생합니다.
원인:
- Next.js 클라이언트/서버 환경 변수 차이: Next.js에서
NEXT_PUBLIC_접두사가 없는 환경 변수는 서버 측 코드에서만 접근할 수 있습니다. 클라이언트 측(브라우저에서 실행되는 코드)에서 접근하려면 반드시NEXT_PUBLIC_접두사를 붙여야 합니다. .env파일 미로드: Python 프로젝트에서python-dotenv와 같은 라이브러리를 사용하여.env파일을 명시적으로 로드하지 않으면, 환경 변수가 적용되지 않습니다.- 캐싱 문제: 환경 변수를 변경한 후 서버를 다시 시작하지 않아 변경 사항이 적용되지 않는 경우가 있습니다.
- 배포 환경 변수 설정 누락: 클라우드 플랫폼(Vercel, AWS, GCP 등)에 배포할 때, 해당 플랫폼의 환경 변수 설정 UI 또는 CLI를 통해 명시적으로 환경 변수를 주입해야 합니다.
.env파일은 Git에 포함되지 않으므로 배포 시 자동으로 적용되지 않습니다.
해결책:
- Next.js에서는 클라이언트 측에서 사용할 환경 변수에
NEXT_PUBLIC_접두사를 반드시 붙이세요. - FastAPI (Python)에서는
dotenv.load_dotenv()를 애플리케이션 시작 부분에 추가하여.env파일을 로드하세요. - 환경 변수를 변경한 후에는 반드시 개발 서버를 다시 시작하세요.
- 배포 시에는 각 클라우드 플랫폼의 환경 변수 관리 기능을 사용하여 정확하게 설정되었는지 확인하세요. Docker를 사용하는 경우,
Dockerfile이나docker-compose.yml에environment섹션을 통해 변수를 주입합니다.
배포 시 발생 가능한 문제 (정적 파일, 경로 설정 등)
문제 상황: 개발 환경에서는 잘 작동하지만, 배포 후에는 이미지가 로드되지 않거나, API 호출이 실패하거나, 페이지를 찾을 수 없다는 오류가 발생합니다.
원인 및 해결책:
- 정적 파일 경로 문제: Next.js에서
public디렉토리에 있는 이미지나 파일은 루트 경로(/)에서 접근 가능합니다. 배포 시에도 이 경로가 올바르게 매핑되는지 확인하세요. 예를 들어,public/image.png는/image.png로 접근해야 합니다. - API URL 불일치: 개발 시에는
localhost를 사용했지만, 배포 시에는 실제 백엔드 서버의 도메인으로 API URL을 변경해야 합니다.NEXT_PUBLIC_API_URL과 같은 환경 변수를 사용하여 유연하게 관리하고, 배포 환경에 맞춰 올바른 값을 주입해야 합니다. - Nginx/프록시 설정 누락: 단일 서버에 Next.js와 FastAPI를 함께 배포하고 Nginx와 같은 웹 서버를 리버스 프록시로 사용하는 경우, Nginx 설정에서 프론트엔드 정적 파일 서빙과
/api경로의 백엔드 프록시 설정이 올바르게 되어 있는지 확인해야 합니다. - 빌드 및 의존성 문제: 배포 환경에 따라 Node.js나 Python 버전이 다르거나,
npm install또는pip install이 제대로 실행되지 않아 필요한 패키지가 없는 경우가 있습니다. Docker를 사용하면 이러한 의존성 문제를 표준화할 수 있습니다. - 데이터베이스 연결 문제: 데이터베이스 호스트, 포트, 사용자명, 비밀번호가 배포 환경에 맞게 정확히 설정되었는지, 그리고 방화벽 규칙이 데이터베이스 접근을 허용하는지 확인해야 합니다.
이러한 문제들은 대부분 환경 설정이나 경로 문제에서 비롯되므로, 각 환경 변수와 설정 파일의 값을 꼼꼼히 확인하고, 배포 로그를 자세히 살펴보는 것이 중요합니다.
결론: Next.js와 FastAPI로 완성하는 강력한 풀스택 개발
지금까지 Next.js와 FastAPI를 연동하여 강력하고 효율적인 풀스택 개발 환경을 구축하는 방법에 대해 자세히 살펴보았습니다. 우리는 각 프레임워크의 강점을 이해하고, 프로젝트를 초기화하며, API 통신을 구현하고, 데이터베이스를 연동하며, 효율적인 개발 워크플로우와 배포 전략까지 다루었습니다. 또한, 개발 과정에서 발생할 수 있는 흔한 문제점들에 대한 해결책도 제시했습니다.
Next.js는 React 기반의 뛰어난 개발자 경험과 성능 최적화 기능을 제공하여 현대적인 프론트엔드 개발의 복잡성을 크게 줄여줍니다. FastAPI는 파이썬의 강력함을 바탕으로 고성능의 비동기 API를 빠르고 안정적으로 구축할 수 있게 돕습니다. 이 두 기술 스택의 조합은 개발 생산성을 극대화하고, 확장성 있는 애플리케이션을 구축하는 데 필요한 모든 요소를 제공합니다.
이 가이드가 여러분이 Next.js와 FastAPI를 활용하여 풀스택 프로젝트를 성공적으로 시작하고 운영하는 데 실질적인 도움이 되기를 바랍니다. 문제 해결 중심의 접근 방식과 구체적인 코드 예시들을 통해, 여러분은 복잡한 풀스택 환경 구축의 어려움을 극복하고 더 나은 개발 경험을 할 수 있을 것입니다. 여러분의 프로젝트가 더욱 견고하고 사용자 친화적으로 발전하길 응원합니다.
이 가이드에 대해 궁금한 점이나 공유하고 싶은 팁이 있다면 언제든지 댓글로 남겨주세요! 여러분의 경험과 질문은 다른 개발자들에게 큰 도움이 됩니다.
📌 함께 읽으면 좋은 글
- [커리어 취업] 합격률 높이는 개발자 이력서 작성 가이드: 프로젝트 경험 정리와 기술 스택 강조 전략
- [기술 리뷰] 웹 프론트엔드 메타 프레임워크 비교: Next.js, Remix, Astro 특징 및 선택 가이드
- [커리어 취업] 개발자 포트폴리오 구축 실전 가이드: 차별화된 프로젝트로 취업 경쟁력 높이기
이 글이 도움이 되셨다면 공감(♥)과 댓글로 응원해 주세요!
궁금한 점이나 다루었으면 하는 주제가 있다면 댓글로 남겨주세요.
'튜토리얼' 카테고리의 다른 글
| Minikube를 활용한 로컬 쿠버네티스 환경 구축: 웹 애플리케이션 배포 완벽 가이드 (0) | 2026.03.27 |
|---|---|
| Docker Compose를 활용한 다중 서비스 로컬 개발 환경 구축: 빠르고 효율적인 컨테이너 기반 워크플로우 가이드 (0) | 2026.03.27 |
| Docker Compose로 로컬 개발 환경 구축: 다중 서비스 연동 실전 가이드 (0) | 2026.03.19 |
| Playwright E2E 테스트 환경 구축: 웹 자동화 실전 가이드 (0) | 2026.03.18 |
| NestJS Socket.IO 실시간 채팅 백엔드 구축 가이드: 단계별 완전 정복 (0) | 2026.03.17 |