🛠️ React + Node.js 풀스택 앱 배포하기 – 완성까지 한번에!

개발 에러 해결 가이드 - FixLog 노트

프로젝트 소개 및 목표

React + Node.js 풀스택 앱 배포하기는 프론트엔드부터 백엔드, 그리고 실제 배포까지 전 과정을 경험할 수 있는 실전 프로젝트입니다. 이 가이드를 통해 React로 사용자 인터페이스를 구축하고, Node.js와 Express로 RESTful API 서버를 만들며, MongoDB로 데이터를 관리하는 완전한 웹 애플리케이션을 개발합니다. 최종적으로 Vercel과 Render를 활용해 무료로 배포하는 방법까지 배우게 됩니다. 이 프로젝트는 포트폴리오에 추가할 수 있는 실용적인 풀스택 애플리케이션을 만들고, 실무에서 필요한 배포 경험을 쌓을 수 있는 최적의 학습 과정입니다.

필요한 기술 스택

이 프로젝트를 구현하기 위해 다음 기술들이 필요합니다:

  • 프론트엔드: React 18+, React Router, Axios, Tailwind CSS
  • 백엔드: Node.js 18+, Express.js, MongoDB, Mongoose
  • 인증: JWT (JSON Web Token), bcrypt
  • 배포: Vercel (프론트엔드), Render (백엔드), MongoDB Atlas (데이터베이스)
  • 개발 도구: Git, npm/yarn, Postman, VS Code

Node.js와 React에 대한 기본적인 이해가 있다면 충분히 따라올 수 있습니다.

프로젝트 셋업

먼저 프로젝트 구조를 설정하겠습니다. 하나의 저장소에 클라이언트와 서버를 분리하는 모노레포 구조를 사용합니다.

# 프로젝트 루트 디렉토리 생성
mkdir fullstack-deploy-app
cd fullstack-deploy-app

# 백엔드 초기화
mkdir server
cd server
npm init -y
npm install express mongoose dotenv cors jsonwebtoken bcryptjs
npm install -D nodemon

# 프론트엔드 초기화
cd ..
npx create-react-app client
cd client
npm install react-router-dom axios
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

프로젝트 루트에 .gitignore 파일을 생성하여 node_modules/, .env 파일을 제외합니다. 이제 기본 구조가 완성되었으니 본격적인 구현을 시작하겠습니다.

단계별 구현 과정

1단계: MongoDB Atlas 설정 및 백엔드 모델 생성

먼저 MongoDB Atlas에서 무료 클러스터를 생성하고 연결 문자열을 받습니다. server/.env 파일을 생성합니다:

MONGODB_URI=mongodb+srv://username:password@cluster.mongodb.net/fullstack-app
JWT_SECRET=your_super_secret_key_here
PORT=5000

사용자 모델을 정의합니다 (server/models/User.js):

const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');

const userSchema = new mongoose.Schema({
  username: {
    type: String,
    required: true,
    unique: true,
    trim: true,
    minlength: 3
  },
  email: {
    type: String,
    required: true,
    unique: true,
    lowercase: true
  },
  password: {
    type: String,
    required: true,
    minlength: 6
  },
  createdAt: {
    type: Date,
    default: Date.now
  }
});

// 비밀번호 해싱 미들웨어
userSchema.pre('save', async function(next) {
  if (!this.isModified('password')) return next();
  this.password = await bcrypt.hash(this.password, 12);
  next();
});

// 비밀번호 검증 메서드
userSchema.methods.comparePassword = async function(candidatePassword) {
  return await bcrypt.compare(candidatePassword, this.password);
};

module.exports = mongoose.model('User', userSchema);

2단계: Express 서버 및 인증 API 구현

server/index.js 파일을 생성하여 Express 서버를 설정합니다:

const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
const jwt = require('jsonwebtoken');
require('dotenv').config();

const User = require('./models/User');

const app = express();

// 미들웨어
app.use(cors());
app.use(express.json());

// MongoDB 연결
mongoose.connect(process.env.MONGODB_URI)
  .then(() => console.log('MongoDB 연결 성공'))
  .catch(err => console.error('MongoDB 연결 실패:', err));

// JWT 인증 미들웨어
const authenticateToken = (req, res, next) => {
  const token = req.headers['authorization']?.split(' ')[1];
  if (!token) return res.status(401).json({ message: '인증 토큰이 필요합니다' });

  jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
    if (err) return res.status(403).json({ message: '유효하지 않은 토큰입니다' });
    req.user = user;
    next();
  });
};

// 회원가입 API
app.post('/api/auth/register', async (req, res) => {
  try {
    const { username, email, password } = req.body;
    
    const existingUser = await User.findOne({ $or: [{ email }, { username }] });
    if (existingUser) {
      return res.status(400).json({ message: '이미 존재하는 사용자입니다' });
    }

    const user = new User({ username, email, password });
    await user.save();

    const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: '7d' });
    res.status(201).json({ token, user: { id: user._id, username, email } });
  } catch (error) {
    res.status(500).json({ message: '회원가입 실패', error: error.message });
  }
});

// 로그인 API
app.post('/api/auth/login', async (req, res) => {
  try {
    const { email, password } = req.body;
    
    const user = await User.findOne({ email });
    if (!user || !(await user.comparePassword(password))) {
      return res.status(401).json({ message: '이메일 또는 비밀번호가 잘못되었습니다' });
    }

    const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: '7d' });
    res.json({ token, user: { id: user._id, username: user.username, email } });
  } catch (error) {
    res.status(500).json({ message: '로그인 실패', error: error.message });
  }
});

// 보호된 라우트 예제
app.get('/api/user/profile', authenticateToken, async (req, res) => {
  try {
    const user = await User.findById(req.user.id).select('-password');
    res.json(user);
  } catch (error) {
    res.status(500).json({ message: '사용자 정보 조회 실패' });
  }
});

const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`서버가 포트 ${PORT}에서 실행 중입니다`));

server/package.json에 스크립트를 추가합니다:

"scripts": {
  "start": "node index.js",
  "dev": "nodemon index.js"
}

3단계: React 프론트엔드 구현

먼저 Tailwind CSS를 설정합니다 (client/tailwind.config.js):

module.exports = {
  content: ["./src/**/*.{js,jsx,ts,tsx}"],
  theme: {
    extend: {},
  },
  plugins: [],
}

client/src/index.css에 Tailwind 지시문을 추가합니다:

@tailwind base;
@tailwind components;
@tailwind utilities;

API 통신을 위한 axios 인스턴스를 생성합니다 (client/src/api/axios.js):

import axios from 'axios';

const instance = axios.create({
  baseURL: process.env.REACT_APP_API_URL || 'http://localhost:5000/api'
});

// 요청 인터셉터로 토큰 자동 추가
instance.interceptors.request.use((config) => {
  const token = localStorage.getItem('token');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

export default instance;

인증 컨텍스트를 생성합니다 (client/src/context/AuthContext.js):

import React, { createContext, useState, useContext, useEffect } from 'react';
import axios from '../api/axios';

const AuthContext = createContext();

export const useAuth = () => useContext(AuthContext);

export const AuthProvider = ({ children }) => {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const token = localStorage.getItem('token');
    if (token) {
      loadUser();
    } else {
      setLoading(false);
    }
  }, []);

  const loadUser = async () => {
    try {
      const response = await axios.get('/user/profile');
      setUser(response.data);
    } catch (error) {
      localStorage.removeItem('token');
    } finally {
      setLoading(false);
    }
  };

  const login = async (email, password) => {
    const response = await axios.post('/auth/login', { email, password });
    localStorage.setItem('token', response.data.token);
    setUser(response.data.user);
    return response.data;
  };

  const register = async (username, email, password) => {
    const response = await axios.post('/auth/register', { username, email, password });
    localStorage.setItem('token', response.data.token);
    setUser(response.data.user);
    return response.data;
  };

  const logout = () => {
    localStorage.removeItem('token');
    setUser(null);
  };

  return (
    
      {children}
    
  );
};

로그인 컴포넌트를 구현합니다 (client/src/pages/Login.js):

import React, { useState } from 'react';
import { useNavigate, Link } from 'react-router-dom';
import { useAuth } from '../context/AuthContext';

const Login = () => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [error, setError] = useState('');
  const { login } = useAuth();
  const navigate = useNavigate();

  const handleSubmit = async (e) => {
    e.preventDefault();
    setError('');
    try {
      await login(email, password);
      navigate('/dashboard');
    } catch (err) {
      setError(err.response?.data?.message || '로그인에 실패했습니다');
    }
  };

  return (
    

로그인

{error &&
{error}
}
setEmail(e.target.value)} className="w-full px-3 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500" required />
setPassword(e.target.value)} className="w-full px-3 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500" required />

계정이 없으신가요? 회원가입

); }; export default Login;

4단계: 라우팅 및 메인 앱 구성

client/src/App.js를 구성합니다:

import React from 'react';
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import { AuthProvider, useAuth } from './context/AuthContext';
import Login from './pages/Login';
import Register from './pages/Register';
import Dashboard from './pages/Dashboard';

const PrivateRoute = ({ children }) => {
  const { user, loading } = useAuth();
  if (loading) return 
로딩 중...
; return user ? children : ; }; function App() { return ( } /> } /> } /> } /> ); } export default App;

테스트 및 배포

로컬 테스트

먼저 로컬 환경에서 애플리케이션을 테스트합니다:

# 터미널 1: 백엔드 실행
cd server
npm run dev

# 터미널 2: 프론트엔드 실행
cd client
npm start

브라우저에서 http://localhost:3000으로 접속하여 회원가입, 로그인, 대시보드 접근을 테스트합니다.

백엔드 배포 (Render)

React + Node.js 풀스택 앱 배포하기의 핵심 단계입니다. Render.com에 접속하여 다음 단계를 진행합니다:

  1. New > Web Service 선택
  2. GitHub 저장소 연결 (server 폴더)
  3. 환경 설정:
    • Build Command: npm install
    • Start Command: npm start
    • Environment Variables에 MONGODB_URI, JWT_SECRET 추가
  4. Create Web Service 클릭

배포된 URL을 복사합니다 (예: https://your-app.onrender.com).

프론트엔드 배포 (Vercel)

client/.env.production 파일을 생성합니다:

REACT_APP_API_URL=https://your-app.onrender.com/api

Vercel CLI로 배포합니다:

npm install -g vercel
cd client
vercel --prod

또는 Vercel 웹사이트에서 GitHub 저장소를 연결하여 자동 배포를 설정할 수 있습니다. 환경 변수 REACT_APP_API_URL을 Vercel 대시보드에서 설정해야 합니다.

마무리 및 확장 아이디어

축하합니다! React + Node.js 풀스택 앱 배포하기 프로젝트를 완성했습니다. 이제 실제 운영 환경에서 동작하는 풀스택 애플리케이션을 가지게 되었습니다. 이 기본 구조를 바탕으로 다음과 같은 기능을 추가할 수 있습니다:

  • 소셜 로그인: Google, GitHub OAuth 통합
  • 파일 업로드: Cloudinary 또는 AWS S3 활용
  • 실시간 기능: Socket.io로 채팅 또는 알림 구현
  • 데이터 관리: CRUD 기능이 있는 게시판 또는 할 일 목록 추가
  • 페이지네이션 및 검색: 데이터 조회 기능 향상
  • CI/CD: GitHub Actions로 자동 배포 파이프라인 구축

이 프로젝트는 포트폴리오의 핵심 항목이 될 수 있으며, 실무에서 요구하는 배포 경험을 제공합니다. 계속해서 기능을 추가하고 개선하면서 실력을 키워나가세요!

📚 함께 읽으면 좋은 글

1

React + Node.js 풀스택 앱 배포하기 – 완성까지 한번에!

📂 프로젝트 아이디어
📅 2025. 10. 7.
🎯 React + Node.js 풀스택 앱 배포하기

2

React + Node.js 풀스택 앱 배포하기 – 완성까지 한번에!

📂 프로젝트 아이디어
📅 2025. 10. 5.
🎯 React + Node.js 풀스택 앱 배포하기

3

React + Node.js 풀스택 앱 배포하기 – 완성까지 한번에!

📂 프로젝트 아이디어
📅 2025. 10. 3.
🎯 React + Node.js 풀스택 앱 배포하기

4

React + Node.js 풀스택 앱 배포하기 – 완성까지 한번에!

📂 프로젝트 아이디어
📅 2025. 10. 2.
🎯 React + Node.js 풀스택 앱 배포하기

5

30분만에 만드는 Todo App 완성 가이드 – 완성까지 한번에!

📂 프로젝트 아이디어
📅 2025. 10. 5.
🎯 30분만에 만드는 Todo App 완성 가이드

💡 위 글들을 통해 더 깊이 있는 정보를 얻어보세요!

📢 이 글이 도움되셨나요? 공유해주세요!

여러분의 공유 한 번이 더 많은 사람들에게 도움이 됩니다 ✨


📘 페이스북


🐦 트위터


✈️ 텔레그램

🔥 공유할 때마다 블로그 성장에 큰 힘이 됩니다! 감사합니다 🙏

💬 여러분의 소중한 의견을 들려주세요!

이 글을 읽고 새롭게 알게 된 정보가 있다면 공유해주세요!

💡
유용한 정보 공유

궁금한 점 질문

🤝
경험담 나누기

👍
의견 표현하기

⭐ 모든 댓글은 24시간 내에 답변드리며, 여러분의 의견이 다른 독자들에게 큰 도움이 됩니다!
🎯 건설적인 의견과 경험 공유를 환영합니다 ✨

🔔 블로그 구독하고 최신 글을 받아보세요!

📚
다양한 주제
17개 카테고리

정기 업데이트
하루 3회 발행

🎯
실용적 정보
바로 적용 가능

💡
최신 트렌드
2025년 기준

🌟 프로젝트 아이디어부터 다양한 실생활 정보까지!
매일 새로운 유용한 콘텐츠를 만나보세요 ✨

📧 RSS 구독 | 🔖 북마크 추가 | 📱 모바일 앱 알림 설정
지금 구독하고 놓치는 정보 없이 업데이트 받아보세요!

📱 전체 버전 보기