728x90

기존 프로젝트는 Thymeleaf 기반의 간단한 페이지로 구성되어 있었는데
프론트 협업을 대비해서 연습해보고 싶어서 Next.js 기반으로 리팩토링‼
1. Next.js 란?
- React를 위해 만든 오픈 소스 JavaScript 웹 프레임워크
- React에는 없는 기능 제공
- 서버 사이드 렌더링(SSR; Server-Side Rendering)
- 정적 사이트 생성(SSG; Static Site Generation)
- 증분 정적 재생성(ISR; Incremental Static Regeneration)
1.1. SSR vs CSR
| SSR (Server-Side Rendering) | CSR (Client-Server Rendering) | |
| JavaScript 로딩 | 서버에서 JS 로딩 → 완성된 HTML 클라이언트로 전달 (초기 로딩 속도 빠름) |
브라우저에 JS 정보 가진 빈 HTML 전달 → 브라우저가 JS 로딩하여 UI 빌드 (초기 로드 완료되면 렌더링 빠름) |
| SEO (Search Engine Optimization) |
HTML 파일에 모든 정보가 포함되어 있어 봇이 데이터 수집 가능 |
초기 HTML 파일이 비어있어 봇이 데이터 수집하기 어려움 |
1.2. React vs Vue vs Next.js
| React | Vue | Next.js | |
| 개발 유형 | 라이브러리 | 프레임워크 | React 기반 프레임워크 |
| 학습 곡선 | 중간 | 낮음 | 중간 |
| 렌더링 방식 | CSR | CSR | SSR, SSG |
| 라우팅 | 별도 라이브러리 필요 | Vue Router 필요 | 파일 기반 라우팅 |
| 상태 관리 | Redux, Context API | Vuex | Redux, Context API |
| 사용 사례 | 대규모 SPA | 소규모~중규모 프로젝트 | SEO가 필요한 프로젝트 |
| 커뮤니티 및 생태계 | 매우 활발 | 활발 | 활발 (React 기반) |
2. Next.js 프로젝트 준비
2.0. 최종 목표 디렉토리
2025-posleep/
├─ backend/
├─ frontend/ # ← Next.js 프로젝트 추가
├─ infra/
│ ├─ docker-compose.yml
│ └─ nginx/
│ └─ nginx.conf
└─ ...
- SpringBoot + Thymeleaf 구조 →→→ Next.js + 백엔드 API (SpringBoot)
- 모노레포 (Monorepo)
- CI/CD 통합을 위한 구성
- frontend, backend API 프로젝트를 하나의 레포지토리에서 관리
- Spring root를 2025-posleep → 2025-posleep/backend로 변경
2.1. 설치 및 프로젝트 생성
- nvm + Node 20 LTS 설치
- 프로젝트 생성 : npm create-next-app
- 프로젝트 이름 : frontend
- Turbopack : Next.js 팀이 만든 Rust 기반 초고속 번들러 → 협업 대비 연습이기 때문에 안정성 중요하여 설치 ❌
syun@SyunMac 2025-posleep % npx create-next-app@latest frontend \
--ts --eslint --tailwind --app --src-dir --import-alias "@/*"
Need to install the following packages:
create-next-app@15.5.3
Ok to proceed? (y) y
✔ Would you like to use Turbopack? (recommended) … No / Yes
2.2. 로컬 백엔드 API 연동
- 개발 중에는 백엔드(로컬 8080)로 붙도록 환경 변수 사용 (.env.local)
- 환경변수를 process.env로 자동으로 로드 (ex. process.env.NEXT_PUBLIC_BACKEND_URL)
- 운영 환경 백엔드 URL은 docker-compose.yml 에 설정하여 주입 (3.3 참고)
# frontend/.env.local
NEXT_PUBLIC_API_BASE_URL=http://localhost:8080
- 개발 중 CORS 없이 호출하도록 next.config.ts에 rewrites 추가
import type { NextConfig } from "next";
import { PHASE_DEVELOPMENT_SERVER } from "next/constants";
const base: NextConfig = {
reactStrictMode: true,
};
// 개발 모드에서만 /api → 8080 프록시
const config = (phase: string): NextConfig => {
if (phase === PHASE_DEVELOPMENT_SERVER) {
return {
...base,
async rewrites() {
return [
{ source: "/api/:path*", destination: "http://localhost:8080/:path*" },
];
},
};
}
return base;
};
export default config;
2.3. 개발 서버 실행
cd frontend
npm run dev
3. 프론트엔드 Docker 컨테이너화
3.1. standalone 빌드 설정
- next.config.ts 파일에서 standalone 빌드 설정
- 빌드 시 .next/standalone 디렉토리 생성 : 서버 실행에 필요한 최소한의 파일만 분리
- EC2나 Docker 같은 서버 환경에서 가볍고 빠르게 실행 가능
import type { NextConfig } from "next";
import { PHASE_DEVELOPMENT_SERVER } from "next/constants";
const base: NextConfig = {
reactStrictMode: true,
output: "standalone",
};
const config = (phase: string): NextConfig => {
if (phase === PHASE_DEVELOPMENT_SERVER) {
return {
...base,
async rewrites() {
return [
{ source: "/api/:path*", destination: "http://localhost:8080/:path*" },
];
},
};
}
return base;
};
export default config;
3.2. Dockerfile 작성
- frontend/Dockerfile
# ========= 1) Build stage =========
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
# 프로덕션 빌드 (standalone 산출물 생성)
RUN npm run build
# ========= 2) Runtime stage =========
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=3000
EXPOSE 3000
# 보안상 node 유저 사용
RUN addgroup -S nodejs && adduser -S nextjs -G nodejs
# standalone 서버 코드 & 정적 리소스 복사
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
USER nextjs
CMD ["node", "server.js"]
3.3. docker-compose에 프론트 추가
- infra/docker-compose.yml
version: "3.9"
services:
api:
image: syun32/posleep-api:prod
container_name: posleep-api
env_file:
- /opt/posleep/app.env
volumes:
- /opt/posleep/keys:/opt/posleep/keys:ro
expose:
- "8080"
restart: unless-stopped
environment:
- JAVA_OPTS=-XX:MaxRAMPercentage=75 -XX:+UseG1GC
- TZ=Asia/Seoul
networks:
- posleep-net
frontend:
build:
context: ../frontend
dockerfile: Dockerfile
image: syun32/posleep-frontend:prod
container_name: posleep-frontend
expose:
- "3000"
restart: unless-stopped
environment:
- NEXT_PUBLIC_API_BASE_URL=http://api:8080
- TZ=Asia/Seoul
networks:
- posleep-net
depends_on:
- api
nginx:
image: nginx:1.27-alpine
container_name: posleep-nginx
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
ports:
- "80:80"
restart: unless-stopped
depends_on:
- frontend
- api
networks:
- posleep-net
networks:
posleep-net:
driver: bridge
4. Nginx 프록시 설정
- infra/nginx/nginx.conf
- 프록시 설정
- 기본 트래픽 → 프론트 (외부에는 80만 오픈, 3000은 컨테이너 내부 네트워크 통신)
- /api → 백엔드 (외부에 8080 오픈 유지: 테스트용)
worker_processes auto;
events { worker_connections 1024; }
http {
upstream frontend {
server posleep-frontend:3000;
}
upstream api {
server posleep-api:8080;
}
server {
listen 80;
server_name _;
# Next.js 프론트
location / {
proxy_pass http://frontend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Spring Boot API
location /api/ {
proxy_pass http://api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}
5. GitHub Actions Workflow 변경
- .github/workflows/cicd.yml 변경
- 서버 env에 프론트 이미지 이름/태그 추가
- 프론트 빌드/푸시 Job 추가
- deploy Job은 두 이미지(BE / FE) 모두 푸시된 후에 실행
99. 접속 테스트 ⭐️ 성공 ⭐️
- EC2-IP/ → 프론트엔드(Next.js) 접속 성공

- EC2-IP/api → 백엔드(Spring) 접속 성공

https://github.com/syun32/2025-posleep
GitHub - syun32/2025-posleep
Contribute to syun32/2025-posleep development by creating an account on GitHub.
github.com
📌 References
https://subtlething.tistory.com/115#google_vignette
https://velog.io/@hyunnzl/React-Vue-Next.js-%EC%84%A4%EB%AA%85%EA%B3%BC-%EC%B0%A8%EC%9D%B4%EC%A0%90
https://velog.io/@milkboy2564/Next.js-env%ED%99%98%EA%B2%BD-%EB%B3%80%EC%88%98-%EC%A0%95%EB%A6%AC
728x90
'FrontEnd' 카테고리의 다른 글
| [React] 리액트의 프로퍼티(props) / useState / 이벤트 핸들러(onChange, onClick) (0) | 2025.09.17 |
|---|---|
| [React] 리액트(React)란? : 정의 / 실행환경 구축 / JSX 기본 문법 (0) | 2025.09.17 |
| [CSS] 티스토리 코드블럭 여백, 테두리 없애기 : pre / !important (0) | 2022.09.30 |
| [CSS] CSS3 (4) : 버튼, UI, 다중 칼럼 레이아웃, 플렉서블 박스 레이아웃, 미디어 쿼리 (0) | 2021.07.10 |
| [CSS] CSS3 (3) : 2D / 3D Transform, Transition, Animation (0) | 2021.07.07 |