๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

Front-end

Express ์ปค์Šคํ…€ ์„œ๋ฒ„๋ฅผ Next.js API Routes๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํ•˜๊ธฐ

๋“ค์–ด๊ฐ€๋ฉฐ

๋ ˆ๊ฑฐ์‹œ ์ฝ”๋“œ๋ฅผ ๋ฆฌํŒฉํ† ๋งํ•  ๋•Œ ๊ฐ€์žฅ ์–ด๋ ค์šด ์ ์€ "์™œ ์ด๋ ‡๊ฒŒ ๋งŒ๋“ค์–ด์กŒ์„๊นŒ?"๋ฅผ ์ดํ•ดํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ œ๊ฐ€ ๊ฐœ๋ฐœํ•˜๊ณ  ์žˆ๋Š” WMS(Warehouse Management System) ํ”„๋กœ์ ํŠธ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€์˜€์Šต๋‹ˆ๋‹ค. Next.js ๊ธฐ๋ฐ˜ ํ”„๋กœ์ ํŠธ์ž„์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  ๋ณ„๋„์˜ Express ์ปค์Šคํ…€ ์„œ๋ฒ„๋ฅผ ์šด์˜ํ•˜๊ณ  ์žˆ์—ˆ๋Š”๋ฐ, ์ดˆ๊ธฐ์—๋Š” API ํ”„๋ก์‹œ ๊ธฐ๋Šฅ์„ ๋น ๋ฅด๊ฒŒ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•œ ์„ ํƒ์ด์—ˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์‹œ๊ฐ„์ด ์ง€๋‚˜๋ฉฐ ์ด ์„ ํƒ์€ ๊ธฐ์ˆ  ๋ถ€์ฑ„๋กœ ์Œ“์—ฌ๊ฐ”๊ณ , ๊ฒฐ๊ตญ 1,056์ค„์˜ ๊ฑฐ๋Œ€ํ•œ ๋‹จ์ผ ํŒŒ์ผ์ด ๋˜์–ด ์œ ์ง€๋ณด์ˆ˜์˜ ๋ฐœ๋ชฉ์„ ์žก์•˜์Šต๋‹ˆ๋‹ค.

๋ฌธ์ œ ์ƒํ™ฉ

1,056์ค„์งœ๋ฆฌ Express ์„œ๋ฒ„์˜ ํ•œ๊ณ„

WMS ํ”„๋ก ํŠธ์—”๋“œ ํ”„๋กœ์ ํŠธ๋Š” Next.js ๊ธฐ๋ฐ˜์ด์ง€๋งŒ, API ํ”„๋ก์‹œ๋ฅผ ์œ„ํ•ด ๋ณ„๋„์˜ Express ์ปค์Šคํ…€ ์„œ๋ฒ„(server/index.js)๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์ด ํŒŒ์ผ์€ 1,056์ค„์— ๋‹ฌํ–ˆ๊ณ , ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฌธ์ œ๋ฅผ ์•ˆ๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

  • Next.js์˜ ๋‚ด์žฅ ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•˜์ง€ ๋ชปํ•จ: Next.js๋Š” API Routes, Middleware ๋“ฑ ์„œ๋ฒ„ ์‚ฌ์ด๋“œ ๊ธฐ๋Šฅ์„ ์ž์ฒด ์ œ๊ณตํ•˜์ง€๋งŒ, Express ์ปค์Šคํ…€ ์„œ๋ฒ„๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ด ๊ธฐ๋Šฅ๋“ค์„ ์˜จ์ „ํžˆ ์“ธ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. Hot Module Replacement(HMR)๋„ ์ œ๋Œ€๋กœ ์ž‘๋™ํ•˜์ง€ ์•Š์•„ ๊ฐœ๋ฐœ ๊ฒฝํ—˜๋„ ์ข‹์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.
  • ๋™์ผํ•œ ์ฝ”๋“œ์˜ ๋ฐ˜๋ณต: GET, POST, PUT, PATCH, DELETE ๊ฐ HTTP ๋ฉ”์„œ๋“œ๋งˆ๋‹ค ๊ฑฐ์˜ ๋™์ผํ•œ ํ”„๋ก์‹œ ๋กœ์ง์ด ๋ณต์‚ฌ-๋ถ™์—ฌ๋„ฃ๊ธฐ ์ˆ˜์ค€์œผ๋กœ ๋ฐ˜๋ณต๋˜๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ํ•˜๋‚˜์˜ ๋ฒ„๊ทธ๋ฅผ ์ˆ˜์ •ํ•˜๋ ค๋ฉด 5๊ณณ์„ ๋ชจ๋‘ ์ฐพ์•„ ๊ณ ์ณ์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ํ”„๋ ˆ์ž„์›Œํฌ ์ด์ค‘ ์˜์กด: Express์™€ Next.js ๋‘ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ๋™์‹œ์— ๊ด€๋ฆฌํ•ด์•ผ ํ–ˆ๊ณ , express, multer ๋“ฑ Express ์ „์šฉ ์˜์กด์„ฑ์ด ๋ถˆํ•„์š”ํ•˜๊ฒŒ ํฌํ•จ๋˜์–ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
  • ๋””๋ฒ„๊น…์˜ ์–ด๋ ค์›€: ํŠน์ • api endpoint๋“ค์„ '/excel'๊ณผ ๊ฐ™์€ ํ•˜๋‚˜์˜ ๊ฒฝ๋กœ๋กœ ํ”„๋ก์‹œํ•˜์—ฌ ๊ฒฝ๋กœ๋ฅผ ๊ฐ€๋กœ์ฑ„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒฝ์šฐ ๋•Œ๋ฌธ์—, ์–ด๋–ค ์š”์ฒญ์ด ์–ด๋””๋กœ ๊ฐ€๋Š”์ง€ ์ถ”์ ํ•˜๊ธฐ ์–ด๋ ค์› ์Šต๋‹ˆ๋‹ค.
  • ๋ฐฐํฌ ๋ณต์žก๋„ ์ฆ๊ฐ€: Dockerfile์—์„œ server/ ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ๋ณ„๋„๋กœ ๋ณต์‚ฌํ•ด์•ผ ํ–ˆ๊ณ , ์‹คํ–‰ ๋ช…๋ น์–ด๋„ node server/index.js๋กœ Next.js ํ‘œ์ค€๊ณผ ๋‹ฌ๋ž์Šต๋‹ˆ๋‹ค. ์‹ ๊ทœ ํŒ€์›์ด ํ•ฉ๋ฅ˜ํ•  ๋•Œ๋งˆ๋‹ค "์™œ Next.js์ธ๋ฐ Express ์„œ๋ฒ„๋ฅผ ๋„์›Œ์•ผ ํ•˜๋‚˜์š”?"๋ผ๋Š” ์งˆ๋ฌธ์„ ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค.
// ๊ธฐ์กด: server/index.js (1,056์ค„)
const express = require("express");
const next = require("next");
const multer = require("multer");

app.prepare().then(() => {
  const server = express();

  // GET, POST, PUT, PATCH, DELETE๋งˆ๋‹ค ๊ฑฐ์˜ ๋™์ผํ•œ ํ”„๋ก์‹œ ์ฝ”๋“œ๊ฐ€ ๋ฐ˜๋ณต...
  server.post("/api/v2/*", async (req, res) => { /* ... */ });
  server.patch("/api/v2/*", async (req, res) => { /* ... */ });
  server.put("/api/v2/*", async (req, res) => { /* ... */ });
  server.delete("/api/v2/*", async (req, res) => { /* ... */ });
  server.get("/api/*", async (req, res) => { /* ... */ });
  // ...์ด๋Ÿฐ ํŒจํ„ด์ด ์ˆ˜๋ฐฑ ์ค„ ๊ณ„์†๋จ
});

ํ•ด๊ฒฐ ๋ฐฉ์•ˆ

์—ญํ•  ๋ถ„์„๋ถ€ํ„ฐ ์‹œ์ž‘

๋ฆฌํŒฉํ† ๋ง์˜ ์ฒซ ๊ฑธ์Œ์€ ํ˜„์žฌ ์‹œ์Šคํ…œ์ด ์‹ค์ œ๋กœ ๋ฌด์—‡์„ ํ•˜๊ณ  ์žˆ๋Š”์ง€ ํŒŒ์•…ํ•˜๋Š” ๊ฒƒ์ด์—ˆ์Šต๋‹ˆ๋‹ค. Express ์ปค์Šคํ…€ ์„œ๋ฒ„๊ฐ€ ์ˆ˜ํ–‰ํ•˜๋˜ ์—ญํ• ์„ ๋ถ„์„ํ•˜๋ฉด ํฌ๊ฒŒ ์„ธ ๊ฐ€์ง€์˜€์Šต๋‹ˆ๋‹ค:

  1. API ํ”„๋ก์‹œ: ํ”„๋ก ํŠธ์—”๋“œ์˜ API ์š”์ฒญ์„ ๋ฐฑ์—”๋“œ ์„œ๋ฒ„๋กœ ์ „๋‹ฌ
  2. ์„ธ์…˜ ์ฒดํฌ: ์ฟ ํ‚ค ๊ธฐ๋ฐ˜ ์ธ์ฆ ํ™•์ธ
  3. ํ—ฌ์Šค ์ฒดํฌ: ์ปจํ…Œ์ด๋„ˆ ์ƒํƒœ ํ™•์ธ ์—”๋“œํฌ์ธํŠธ

๋ณต์žกํ•ด ๋ณด์˜€๋˜ 1,056์ค„์˜ ์ฝ”๋“œ๊ฐ€ ๊ฒฐ๊ตญ ์ด ์„ธ ๊ฐ€์ง€ ๊ธฐ๋Šฅ๋งŒ ์ˆ˜ํ–‰ํ•˜๊ณ  ์žˆ์—ˆ๊ณ , ์ด๋Š” ๋ชจ๋‘ Next.js์˜ ๋„ค์ดํ‹ฐ๋ธŒ ๊ธฐ๋Šฅ์œผ๋กœ ๋Œ€์ฒด ๊ฐ€๋Šฅํ–ˆ์Šต๋‹ˆ๋‹ค.

๊ธฐ์กด (Express)  ๋ณ€๊ฒฝ ํ›„ (Next.js)
server/index.js (1,056์ค„) pages/api/ ๋””๋ ‰ํ† ๋ฆฌ์˜ API Routes
Express ๋ฏธ๋“ค์›จ์–ด๋กœ ์„ธ์…˜ ์ฒดํฌ middleware.ts
server.get("/health-check") pages/api/health-check.ts
multer๋กœ ํŒŒ์ผ ์—…๋กœ๋“œ ์ฒ˜๋ฆฌ formidable + lib/api/fileUploadUtils.ts
node server/index.js next start

์ด ๋ฐฉ์•ˆ์„ ์„ ํƒํ•œ ์ด์œ 

1. ํ”„๋ ˆ์ž„์›Œํฌ ์ผ๊ด€์„ฑ ํ™•๋ณด

Next.js ํ”„๋กœ์ ํŠธ์—์„œ Express๋ฅผ ๋ณ„๋„๋กœ ์šด์˜ํ•˜๋Š” ๊ฒƒ์€ ๋ถˆํ•„์š”ํ•œ ๋ณต์žก์„ฑ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. Next.js๊ฐ€ ์ œ๊ณตํ•˜๋Š” API Routes๋Š” ์„œ๋ฒ„๋ฆฌ์Šค ํ•จ์ˆ˜์ฒ˜๋Ÿผ ๋™์ž‘ํ•˜๋ฉฐ, ๋ณ„๋„์˜ ์„œ๋ฒ„ ์„ค์ • ์—†์ด๋„ API ์—”๋“œํฌ์ธํŠธ๋ฅผ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. 

  • Next.js์˜ ํŒŒ์ผ ๊ธฐ๋ฐ˜ ๋ผ์šฐํŒ… ์‹œ์Šคํ…œ์„ ์ผ๊ด€๋˜๊ฒŒ ์‚ฌ์šฉ
  • ํ”„๋ก ํŠธ์—”๋“œ์™€ ๋ฐฑ์—”๋“œ ๋กœ์ง์„ ๋™์ผํ•œ ๋นŒ๋“œ ํŒŒ์ดํ”„๋ผ์ธ์—์„œ ๊ด€๋ฆฌ
  • Vercel ๋“ฑ ์„œ๋ฒ„๋ฆฌ์Šค ํ”Œ๋žซํผ์œผ๋กœ์˜ ๋ฐฐํฌ ์˜ต์…˜ ํ™•๋ณด

2. ์ฝ”๋“œ ์ค‘๋ณต ์ œ๊ฑฐ์™€ ์œ ์ง€๋ณด์ˆ˜์„ฑ ํ–ฅ์ƒ

๊ธฐ์กด Express ์„œ๋ฒ„์—์„œ๋Š” HTTP ๋ฉ”์„œ๋“œ๋ณ„๋กœ ๊ฑฐ์˜ ๋™์ผํ•œ ํ”„๋ก์‹œ ๋กœ์ง์ด ๋ฐ˜๋ณต๋˜๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ํŒฉํ† ๋ฆฌ ํŒจํ„ด์„ ์ ์šฉํ•œ createProxyHandler ํ•จ์ˆ˜๋ฅผ ๋„์ž…ํ•จ์œผ๋กœ์จ:

  • 1,056์ค„์˜ ์ฝ”๋“œ๋ฅผ ์•ฝ 550์ค„(6๊ฐœ ๋ชจ๋“ˆ)๋กœ ์ถ•์†Œ
  • ๊ณตํ†ต ๋กœ์ง์„ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ชจ๋“ˆ๋กœ ๋ถ„๋ฆฌ
  • ์ƒˆ๋กœ์šด ํ”„๋ก์‹œ ์—”๋“œํฌ์ธํŠธ ์ถ”๊ฐ€ ์‹œ ์„ค์ • ๊ฐ์ฒด๋งŒ ์ž‘์„ฑํ•˜๋ฉด ๋˜๋Š” ๊ตฌ์กฐ

3. Next.js ์—์ฝ”์‹œ์Šคํ…œ๊ณผ์˜ ํ†ตํ•ฉ

Next.js 13+ ๋ฒ„์ „์—์„œ ๋„์ž…๋œ Middleware๋Š” Edge Runtime์—์„œ ์‹คํ–‰๋˜์–ด ๋น ๋ฅธ ์‘๋‹ต์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ์„ธ์…˜ ์ฒดํฌ ๊ฐ™์€ ์ธ์ฆ ๋กœ์ง์„ Middleware๋กœ ๊ตฌํ˜„ํ•˜๋ฉด:

  • ๋ชจ๋“  ํŽ˜์ด์ง€ ์š”์ฒญ์— ์ผ๊ด€๋œ ์ธ์ฆ ๋กœ์ง ์ ์šฉ
  • ํด๋ผ์ด์–ธํŠธ ์š”์ฒญ์ด ํŽ˜์ด์ง€ ๋ Œ๋”๋ง๊นŒ์ง€ ๋„๋‹ฌํ•˜๊ธฐ ์ „์— ์กฐ๊ธฐ ์ฐจ๋‹จ
  • Edge์—์„œ ์‹คํ–‰๋˜์–ด ๋‚ฎ์€ ๋ ˆ์ดํ„ด์‹œ

4. ๋ฐฐํฌ ๋ฐ ์šด์˜ ๋‹จ์ˆœํ™”

ํ‘œ์ค€ Next.js ๊ตฌ์กฐ๋กœ ์ „ํ™˜ํ•˜๋ฉด์„œ:

  • Dockerfile์ด ๋‹จ์ˆœํ•ด์ ธ ์œ ์ง€๋ณด์ˆ˜ ๋ถ€๋‹ด ๊ฐ์†Œ
  • next start ๋ช…๋ น์–ด๋กœ ํ‘œ์ค€ํ™”๋œ ์‹คํ–‰ ๋ฐฉ์‹
  • Next.js ๊ณต์‹ ๋ฌธ์„œ์™€ ์ปค๋ฎค๋‹ˆํ‹ฐ ์ž๋ฃŒ ํ™œ์šฉ ๊ฐ€๋Šฅ

5. ์˜์กด์„ฑ ์ตœ์†Œํ™”

Express์™€ ๊ด€๋ จ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ œ๊ฑฐํ•จ์œผ๋กœ์จ:

  • package-lock.json์—์„œ 645์ค„ ๊ฐ์†Œ
  • ๋ณด์•ˆ ์ทจ์•ฝ์  ๊ด€๋ฆฌ ๋Œ€์ƒ ๊ฐ์†Œ
  • ๋ฒˆ๋“ค ์‚ฌ์ด์ฆˆ ์ตœ์ ํ™”

์‹คํ–‰ ๊ณผ์ •

Step 1: ๊ณตํ†ต ์œ ํ‹ธ๋ฆฌํ‹ฐ ๋ชจ๋“ˆํ™”

๊ฐ€์žฅ ๋จผ์ € ํ•œ ์ผ์€ ๋ฐ˜๋ณต๋˜๋Š” ์ฝ”๋“œ๋ฅผ ์ฐพ์•„๋‚ด๋Š” ๊ฒƒ์ด์—ˆ์Šต๋‹ˆ๋‹ค. GET, POST, PUT, PATCH, DELETE ๊ฐ ๋ฉ”์„œ๋“œ๋งˆ๋‹ค ๊ฑฐ์˜ ๋™์ผํ•œ ๋กœ์ง์ด 5๋ฒˆ ๋ฐ˜๋ณต๋˜๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ์—ญํ• ๋ณ„๋กœ ๋ถ„๋ฆฌํ•˜์—ฌ lib/api/ ๋””๋ ‰ํ† ๋ฆฌ์— ๋ชจ๋“ˆํ™”ํ–ˆ์Šต๋‹ˆ๋‹ค.

lib/api/
โ”œโ”€โ”€ proxyHandler.ts     # ํ”„๋ก์‹œ ํ•ธ๋“ค๋Ÿฌ ํŒฉํ† ๋ฆฌ (312์ค„)
โ”œโ”€โ”€ headerBuilder.ts    # ์š”์ฒญ ํ—ค๋” ๋นŒ๋“œ
โ”œโ”€โ”€ fileUploadUtils.ts  # multipart/form-data ์ฒ˜๋ฆฌ
โ”œโ”€โ”€ responseFilter.ts   # ์‘๋‹ต ํ•„ํ„ฐ๋ง
โ”œโ”€โ”€ responseUtils.ts    # ์‘๋‹ต ์œ ํ‹ธ๋ฆฌํ‹ฐ
โ””โ”€โ”€ serverUtils.ts      # URL ๋นŒ๋“œ, ํ—ค๋” ์ •๋ฆฌ

ํ•ต์‹ฌ์€ ํŒฉํ† ๋ฆฌ ํŒจํ„ด์„ ํ™œ์šฉํ•œ createProxyHandler ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. ์ด ํ•จ์ˆ˜๋Š” ์„ค์ • ๊ฐ์ฒด๋ฅผ ๋ฐ›์•„์„œ API Route ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด:

  • /api/v2/* ๊ฒฝ๋กœ๋Š” GET, POST, PUT, PATCH, DELETE๋ฅผ ๋ชจ๋‘ ์ง€์›
  • /api/basic/* ๊ฒฝ๋กœ๋Š” GET, POST๋งŒ ์ง€์›
  • ํŒŒ์ผ ์—…๋กœ๋“œ๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ๋กœ๋Š” multipart ์˜ต์…˜ ํ™œ์„ฑํ™”

๊ธฐ์กด์—๋Š” ์ด๋Ÿฐ ์ฐจ์ด๋งˆ๋‹ค ๋ณ„๋„์˜ ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ–ˆ์ง€๋งŒ, ์ด์ œ๋Š” ์„ค์ •๋งŒ ๋ฐ”๊ฟ”์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

// lib/api/proxyHandler.ts
export const createProxyHandler = (config: ProxyConfig) => {
  return async (req: NextApiRequest, res: NextApiResponse) => {
    const method = req.method || "GET";
    const pathString = extractPath(req);
    const backendUrl = buildBackendUrl(url);
    const headers = cleanHeaders(req.headers);

    // GET, POST, PUT, PATCH, DELETE๋ฅผ ํ•˜๋‚˜์˜ ํ•ธ๋“ค๋Ÿฌ์—์„œ ์ฒ˜๋ฆฌ
    if (method === "GET") { /* ... */ }
    else if (method === "POST") { /* ... */ }
    // ...
  };
};

Step 2: API Routes๋กœ ์—”๋“œํฌ์ธํŠธ ์ „ํ™˜

Express ๋ผ์šฐํŠธ๋ฅผ Next.js์˜ Dynamic API Routes๋กœ ์ „ํ™˜ํ–ˆ์Šต๋‹ˆ๋‹ค. [...path].ts ํŒจํ„ด์œผ๋กœ ํ•˜์œ„ ๊ฒฝ๋กœ๋ฅผ ๋ชจ๋‘ ์บ์น˜ํ•ฉ๋‹ˆ๋‹ค.

// pages/api/v2/[...path].ts
import { createProxyHandler, apiConfig } from "lib/api/proxyHandler";

export const config = apiConfig; // bodyParser: false

export default createProxyHandler({
  pathPrefix: "/api/v2",
  logPrefix: "[api/v2]",
  allowedMethods: ["GET", "POST", "PUT", "PATCH", "DELETE"],
  multipart: true,
});
// pages/api/basic/[...path].ts
import { createProxyHandler, apiConfig } from "lib/api/proxyHandler";

export const config = apiConfig;

export default createProxyHandler({
  pathPrefix: "/api/basic",
  logPrefix: "[api/basic]",
  allowedMethods: ["GET", "POST"],
  multipart: true,
});

๋‹จ 2๊ฐœ์˜ ํŒŒ์ผ๋กœ ๊ธฐ์กด Express ์„œ๋ฒ„์˜ ์ˆ˜๋ฐฑ ์ค„ ๋ผ์šฐํŒ… ์ฝ”๋“œ๋ฅผ ๋Œ€์ฒดํ–ˆ์Šต๋‹ˆ๋‹ค. ์ƒˆ๋กœ์šด ํ”„๋ก์‹œ ๊ฒฝ๋กœ๊ฐ€ ํ•„์š”ํ•˜๋‹ค๋ฉด ์„ค์ • ๊ฐ์ฒด๋งŒ ์ž‘์„ฑํ•˜์—ฌ ์ƒˆ ํŒŒ์ผ์„ ๋งŒ๋“ค๋ฉด ๋ฉ๋‹ˆ๋‹ค. 10์ค„ ์ด๋‚ด์˜ ์ฝ”๋“œ๋กœ ์™„์ „ํžˆ ๋™์ž‘ํ•˜๋Š” ํ”„๋ก์‹œ๊ฐ€ ์™„์„ฑ๋ฉ๋‹ˆ๋‹ค.

Step 3: Middleware๋กœ ์„ธ์…˜ ์ฒดํฌ

Next.js Middleware๋ฅผ ํ™œ์šฉํ•ด ํŽ˜์ด์ง€ ์ ‘๊ทผ ์‹œ ์„ธ์…˜ ์ฟ ํ‚ค๋ฅผ ํ™•์ธํ•˜๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.

// middleware.ts
export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;

  // ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋Š” ํ†ต๊ณผ
  if (PUBLIC_PATHS.some((path) => pathname.startsWith(path))) {
    return NextResponse.next();
  }

  // ์„ธ์…˜ ์ฟ ํ‚ค๊ฐ€ ์—†์œผ๋ฉด ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ
  const cookieHeader = request.headers.get("cookie") || "";
  if (!cookieHeader.includes("CSESSIONID=")) {
    const loginUrl = new URL("/auth/login", request.url);
    return NextResponse.redirect(loginUrl);
  }

  return NextResponse.next();
}

Step 4: ํ˜ธํ™˜์„ฑ์„ ์œ„ํ•œ rewrites ์„ค์ •

๊ธฐ์กด์— /health-check, /session-check ๊ฒฝ๋กœ๋กœ ์ง์ ‘ ์ ‘๊ทผํ•˜๋˜ ํด๋ผ์ด์–ธํŠธ/์ธํ”„๋ผ ์ฝ”๋“œ๊ฐ€ ์žˆ์œผ๋ฏ€๋กœ, next.config.js์— rewrites๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๊ธฐ์กด ๊ฒฝ๋กœ๋ฅผ ์œ ์ง€ํ–ˆ์Šต๋‹ˆ๋‹ค.

// next.config.js
async rewrites() {
  return [
    { source: "/health-check", destination: "/api/health-check" },
    { source: "/session-check", destination: "/api/session-check" },
  ];
},

Step 5: Webpack ์„ค์ • ์ˆ˜์ •

๊ธฐ์กด์— ์„œ๋ฒ„/ํด๋ผ์ด์–ธํŠธ ๋นŒ๋“œ๋ฅผ ๊ตฌ๋ถ„ํ•˜์ง€ ์•Š๊ณ  splitChunks ์ตœ์ ํ™”๋ฅผ ์ ์šฉํ•˜๊ณ  ์žˆ์—ˆ๋Š”๋ฐ, Express ์„œ๋ฒ„๊ฐ€ ์‚ฌ๋ผ์ง€๋ฉด์„œ ์„œ๋ฒ„ ๋นŒ๋“œ์—์„œ ๋ธŒ๋ผ์šฐ์ € ์ „์šฉ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ(self ์ฐธ์กฐ)๊ฐ€ ํฌํ•จ๋˜์–ด ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ ๋นŒ๋“œ์—์„œ๋งŒ ์ ์šฉํ•˜๋„๋ก ์ˆ˜์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.

// ๋ณ€๊ฒฝ ์ „
if (!dev) {
  config.optimization = { ... };
}

// ๋ณ€๊ฒฝ ํ›„
if (!dev && !isServer) {
  config.optimization = { ... };
}

Step 6: ์‹คํ–‰ ์Šคํฌ๋ฆฝํŠธ ์ „ํ™˜

// package.json
{
  "scripts": {
    "dev": "NODE_ENV=development next dev",           // ๋ณ€๊ฒฝ: node server/index.js → next dev
    "dev:https": "NODE_ENV=development node scripts/dev-server.js",  // ์‹ ๊ทœ: HTTPS ๊ฐœ๋ฐœ์šฉ
    "start": "NODE_ENV=production PORT=8080 next start -p 8080"      // ๋ณ€๊ฒฝ: node server/index.js → next start
  }
}

๋กœ์ปฌ HTTPS ๊ฐœ๋ฐœ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ๋ฅผ ์œ„ํ•ด scripts/dev-server.js๋ฅผ ๋ณ„๋„๋กœ ๋ถ„๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.

Step 7: Dockerfile ๋ฐ ์˜์กด์„ฑ ์ •๋ฆฌ

# ๋ณ€๊ฒฝ ์ „
COPY --from=build /app/server ./server    # Express ์„œ๋ฒ„ ๋””๋ ‰ํ† ๋ฆฌ ๋ณต์‚ฌ
CMD ["npm", "start"]                       # node server/index.js ์‹คํ–‰

# ๋ณ€๊ฒฝ ํ›„
COPY --from=build /app/.next ./.next
COPY --from=build /app/public ./public
COPY --from=build /app/next.config.js .    # rewrites ๋“ฑ ๋Ÿฐํƒ€์ž„์— ํ•„์š”
CMD ["npm", "start"]                       # next start ์‹คํ–‰

๋ถˆํ•„์š”ํ•ด์ง„ ์˜์กด์„ฑ๋„ ์ œ๊ฑฐํ–ˆ์Šต๋‹ˆ๋‹ค:

  • express ์ œ๊ฑฐ
  • multer ์ œ๊ฑฐ → formidable๋กœ ๋Œ€์ฒด

package-lock.json์—์„œ 645์ค„์ด ์ค„์—ˆ์Šต๋‹ˆ๋‹ค.


๋ฐฐํฌ ํ›„ ๋ฐœ์ƒํ•œ ๋ฌธ์ œ์™€ ๋Œ€์ฒ˜

๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ฝ”๋“œ๋ฅผ ๋จธ์ง€ํ•˜๊ณ  ๋ฐฐํฌํ•œ ๋’ค, ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ๋กœ์ปฌ ํ™˜๊ฒฝ์—์„œ๋Š” ์™„๋ฒฝํ•˜๊ฒŒ ๋™์ž‘ํ–ˆ์ง€๋งŒ, ํ”„๋กœ๋•์…˜์—์„œ๋Š” ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ์‹œ์ž‘๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

health-check ์—”๋“œํฌ์ธํŠธ ์ ‘๊ทผ ๋ถˆ๊ฐ€

์ฆ์ƒ: ์ปจํ…Œ์ด๋„ˆ ๋ฐฐํฌ ํ›„ health-check๊ฐ€ ์‹คํŒจํ•˜์—ฌ ์„œ๋น„์Šค๊ฐ€ ์ •์ƒ ๊ฐ€๋™๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

์›์ธ ๋ถ„์„: middleware.ts์—์„œ ์„ธ์…˜ ์ฟ ํ‚ค๊ฐ€ ์—†๋Š” ์š”์ฒญ์„ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธํ•˜๋Š”๋ฐ, /health-check ๊ฒฝ๋กœ๊ฐ€ middleware matcher์— ๊ฑธ๋ฆฌ๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. health-check ์š”์ฒญ์€ ์ฟ ํ‚ค ์—†์ด ๋“ค์–ด์˜ค๋ฏ€๋กœ ๋งค๋ฒˆ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ๋˜์–ด 200 ์‘๋‹ต์„ ๋ฐ›์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.

๋กœ์ปฌ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋Š” health-check๋ฅผ ํ…Œ์ŠคํŠธํ•˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด ๋ฌธ์ œ๋ฅผ ๋ฐœ๊ฒฌํ•˜์ง€ ๋ชปํ–ˆ๊ณ , ํ”„๋กœ๋•์…˜ ๋ฐฐํฌ ํ›„์—์•ผ ์•Œ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์ˆ˜์ •: middleware์˜ matcher ํŒจํ„ด์— health-check๋ฅผ ์ œ์™ธ ๊ฒฝ๋กœ๋กœ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.

// middleware.ts
export const config = {
  matcher: [
    "/((?!api|_next/static|_next/image|favicon.ico|images|fonts|service-worker.js|manifest.json|health-check).*)",
    //                                                                                        ^^^^^^^^^^^^^ ์ถ”๊ฐ€
  ],
};

๋˜ํ•œ Dockerfile์—์„œ next.config.js๋ฅผ ํ”„๋กœ๋•์…˜ ๋Ÿฐํƒ€์ž„์— ํฌํ•จํ•˜๋„๋ก ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค. rewrites ์„ค์ •์ด ๋Ÿฐํƒ€์ž„์— ํ•„์š”ํ•œ๋ฐ ๋นŒ๋“œ ์‹œ์ ์—๋งŒ ํฌํ•จ๋˜์–ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

COPY --from=build /app/next.config.js .
  • ์ธํ”„๋ผ ๋ ˆ๋ฒจ์˜ ์—”๋“œํฌ์ธํŠธ(health-check, metrics ๋“ฑ)๋Š” ์ธ์ฆ ๋กœ์ง์—์„œ ๋ช…์‹œ์ ์œผ๋กœ ์ œ์™ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ๊ณผ ๋™์ผํ•œ ์กฐ๊ฑด์—์„œ ํ…Œ์ŠคํŠธํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ํŠนํžˆ Docker ์ปจํ…Œ์ด๋„ˆ๋กœ ๋ฐฐํฌํ•œ๋‹ค๋ฉด ๋กœ์ปฌ์—์„œ๋„ ์ปจํ…Œ์ด๋„ˆ๋กœ ํ…Œ์ŠคํŠธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๊ฒฐ๊ณผ ์š”์•ฝ

ํ•ญ๋ชฉ ๋ณ€๊ฒฝ ์ „ ๋ณ€๊ฒฝ ํ›„

์„œ๋ฒ„ ์•„ํ‚คํ…์ฒ˜ Express ์ปค์Šคํ…€ ์„œ๋ฒ„ Next.js ๋„ค์ดํ‹ฐ๋ธŒ
ํ”„๋ก์‹œ ์ฝ”๋“œ๋Ÿ‰ 1,056์ค„ (๋‹จ์ผ ํŒŒ์ผ) ~550์ค„ (6๊ฐœ ๋ชจ๋“ˆ)
HTTP ๋ฉ”์„œ๋“œ๋ณ„ ์ฝ”๋“œ ๋ณต์‚ฌ-๋ถ™์—ฌ๋„ฃ๊ธฐ ๋ฐ˜๋ณต ํŒฉํ† ๋ฆฌ ํŒจํ„ด์œผ๋กœ ํ†ตํ•ฉ
ํŒŒ์ผ ์—…๋กœ๋“œ multer (Express ์ „์šฉ) formidable (ํ”„๋ ˆ์ž„์›Œํฌ ๋ฌด๊ด€)
์‹คํ–‰ ๋ฐฉ์‹ node server/index.js next start
์ œ๊ฑฐ๋œ ์˜์กด์„ฑ - express, multer
package-lock.json - 645์ค„ ๊ฐ์†Œ
๋ฐฐํฌ ํ›„ ํ•ซํ”ฝ์Šค - health-check ์ •์ƒ ๋™์ž‘

๋งˆ์น˜๋ฉฐ

1. ์ฝ”๋“œ์˜ ๋ช…ํ™•์„ฑ

  • 1,056์ค„์˜ ๋‹จ์ผ ํŒŒ์ผ → ์—ญํ• ๋ณ„๋กœ ๋ถ„๋ฆฌ๋œ 6๊ฐœ ๋ชจ๋“ˆ
  • "์ด ์ฝ”๋“œ๊ฐ€ ๋ญํ•˜๋Š” ๊ฑฐ์ง€?"๋ผ๋Š” ์งˆ๋ฌธ์ด ์‚ฌ๋ผ์กŒ์Šต๋‹ˆ๋‹ค

2. ๊ฐœ๋ฐœ์ž ๊ฒฝํ—˜ ๊ฐœ์„ 

  • Next.js HMR์ด ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™
  • ํ‘œ์ค€ Next.js ๋ช…๋ น์–ด๋กœ ํ†ต์ผ (next dev, next start)
  • ์‹ ๊ทœ ํŒ€์› ์˜จ๋ณด๋”ฉ ์‹œ ์„ค๋ช…ํ•ด์•ผ ํ•  ๋‚ด์šฉ์ด ์ค„์—ˆ์Šต๋‹ˆ๋‹ค

3. ์œ ์ง€๋ณด์ˆ˜์„ฑ ํ–ฅ์ƒ

  • ์ƒˆ๋กœ์šด ํ”„๋ก์‹œ ์—”๋“œํฌ์ธํŠธ ์ถ”๊ฐ€: 10์ค„ ์ด๋‚ด๋กœ ๊ฐ€๋Šฅ
  • ๋ฒ„๊ทธ ์ˆ˜์ •: ํ•œ ๊ณณ๋งŒ ๊ณ ์น˜๋ฉด ๋ชจ๋“  ๋ฉ”์„œ๋“œ์— ๋ฐ˜์˜
  • Next.js ์—…๊ทธ๋ ˆ์ด๋“œ ์‹œ ํ˜ธํ™˜์„ฑ ๊ฑฑ์ • ๊ฐ์†Œ

4. ๊ธฐ์ˆ  ๋ถ€์ฑ„ ํ•ด์†Œ

  • ๋ถˆํ•„์š”ํ•œ ์˜์กด์„ฑ 645์ค„ ์ œ๊ฑฐ
  • ํ”„๋ ˆ์ž„์›Œํฌ ์ด์ค‘ ์˜์กด ๋ฌธ์ œ ํ•ด๊ฒฐ
  • Dockerfile ๋‹จ์ˆœํ™”

๋ ˆ๊ฑฐ์‹œ ์ฝ”๋“œ๋ฅผ ๋ฆฌํŒฉํ† ๋งํ•˜๋Š” ๊ฒƒ์€ ํ•ญ์ƒ ๋‘๋ ค์šด ์ผ์ž…๋‹ˆ๋‹ค.

"ํ˜น์‹œ ๋‚ด๊ฐ€ ๋ชจ๋ฅด๋Š” ์ค‘์š”ํ•œ ๋กœ์ง์ด ์žˆ๋Š” ๊ฑด ์•„๋‹๊นŒ?", "๋ฐฐํฌ ํ›„ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธฐ๋ฉด ์–ด๋–กํ•˜์ง€?" ๊ฐ™์€ ๋ง‰์—ฐํ•œ ๊ฑฑ์ •์ด ์•ž์„ญ๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์ด๋ฒˆ ๊ฒฝํ—˜์„ ํ†ตํ•ด ๋ฐฐ์šด ๊ฒƒ์€ ํ˜„์žฌ ์‹œ์Šคํ…œ์ด ์‹ค์ œ๋กœ ๋ฌด์—‡์„ ํ•˜๋Š”์ง€ ์ •ํ™•ํžˆ ํŒŒ์•…ํ•˜๋ฉด ์ƒ๊ฐ๋ณด๋‹ค ๋‹จ์ˆœํ•˜๊ฒŒ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. 1,056์ค„์˜ ์ฝ”๋“œ๊ฐ€ ์‚ฌ์‹ค์€ "ํ”„๋ก์‹œ, ์„ธ์…˜ ์ฒดํฌ, ํ—ฌ์Šค ์ฒดํฌ" ์„ธ ๊ฐ€์ง€๋งŒ ํ•˜๊ณ  ์žˆ์—ˆ๊ณ , ์ด๋Š” ๋ชจ๋‘ Next.js๋กœ ๋” ๊ฐ„๊ฒฐํ•˜๊ฒŒ ๊ตฌํ˜„ ๊ฐ€๋Šฅํ–ˆ์Šต๋‹ˆ๋‹ค. ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ตœ๋Œ€ํ•œ ํ™œ์šฉํ•˜๋Š” ๊ฒƒ์ด ์žฅ๊ธฐ์ ์œผ๋กœ ์œ ์ง€๋ณด์ˆ˜ ๊ฐ€๋Šฅํ•œ ์ฝ”๋“œ๋ฅผ ๋งŒ๋“œ๋Š” ์ง€๋ฆ„๊ธธ์ด๋ผ๋Š” ๊ฒƒ์„ ๋‹ค์‹œ ํ•œ๋ฒˆ ๋А๊ผˆ์Šต๋‹ˆ๋‹ค.