FSD ์•„ํ‚คํ…์ฒ˜์™€ ํ•จ๊ป˜ ์ƒ๊ฐํ•ด๋ณด๊ธฐ

FSD ์™€ ๋ฐฐ๋Ÿด ํŒŒ์ผ

 

Vue3์—์„œ React.js๋กœ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ํ•˜๋ฉด์„œ Feature-Sliced Design(FSD) ์•„ํ‚คํ…์ฒ˜๋ฅผ ๋„์ž…ํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ ๊ณผ์ •์—์„œ ๋ฐฐ๋Ÿด ํŒŒ์ผ(barrel files)์— ๋Œ€ํ•œ ๊ณ ๋ฏผ์ด ์ƒ๊ฒผ๊ณ , ์ตœ๊ทผ ๋ช‡ ๋…„ ์‚ฌ์ด์— ์ด์— ๋Œ€ํ•œ ๊ด€์ ์ด ๋งŽ์ด ๋ฐ”๋€Œ์—ˆ๋‹ค๋Š” ๊ฒƒ์„ ๋ฐœ๊ฒฌํ–ˆ์Šต๋‹ˆ๋‹ค.

 

๋ฐฐ๋Ÿด ํŒŒ์ผ์ด๋ž€?

๋ฐฐ๋Ÿด ํŒŒ์ผ์€ ์—ฌ๋Ÿฌ ๋ชจ๋“ˆ์„ ํ•˜๋‚˜์˜ ํŒŒ์ผ(์ฃผ๋กœ index.js ๋˜๋Š” index.ts)์—์„œ ๋‹ค์‹œ ๋‚ด๋ณด๋‚ด๋Š” ํŒจํ„ด์ž…๋‹ˆ๋‹ค. ์ด ํŒจํ„ด์€ ์ฝ”๋“œ ์ •๋ฆฌ์™€ import ๋ฌธ์„ ๋‹จ์ˆœํ™”ํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค.

 

๋ฐฐ๋Ÿด ํŒŒ์ผ ์ „ ํ›„

 

๋ฐฐ๋Ÿด ํŒŒ์ผ ์—†์ด:

// ๊ฐ ํŒŒ์ผ์—์„œ ๊ฐœ๋ณ„์ ์œผ๋กœ ๊ฐ€์ ธ์˜ค๊ธฐ 
import { Button } from "../components/Button"; 
import { TextField } from "../components/TextField"; 
import { Checkbox } from "../components/Checkbox";

 

๋ฐฐ๋Ÿด ํŒŒ์ผ ์‚ฌ์šฉ ์‹œ:

// ํ•˜๋‚˜์˜ ๊ฒฝ๋กœ์—์„œ ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ ๊ฐ€์ ธ์˜ค๊ธฐ 
import { Button, TextField, Checkbox } from "../components";

 

๋ฐฐ๋Ÿด ํŒŒ์ผ์˜ ์‹ค์ œ ๊ตฌํ˜„

์ผ๋ฐ˜์ ์ธ ๋ฐฐ๋Ÿด ํŒŒ์ผ (components/index.ts):

// ์™€์ผ๋“œ์นด๋“œ ๋‚ด๋ณด๋‚ด๊ธฐ (๊ถŒ์žฅํ•˜์ง€ ์•Š์Œ) 
export * from './Button'; 
export * from './TextField'; 
export * from './Checkbox'; 

// ๋˜๋Š” ๋ช…์‹œ์  ๋‚ด๋ณด๋‚ด๊ธฐ (๊ถŒ์žฅ) 
export { Button } from './Button'; 
export { TextField } from './TextField'; 
export { Checkbox } from './Checkbox';

 

๋ฐฐ๋Ÿด ํŒŒ์ผ์˜ ์„ฑ๋Šฅ ๋ฌธ์ œ

Marvin์˜ JavaScript ์ƒํƒœ๊ณ„ ์†๋„ ๊ฐœ์„ : ๋ฐฐ๋Ÿด ํŒŒ์ผ ๋ฌธ์ œ ๊ธ€์—์„œ ์ง€์ ํ•œ ๋ฐ”์™€ ๊ฐ™์ด, ๋ฐฐ๋Ÿด ํŒŒ์ผ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์„ฑ๋Šฅ ๋ฌธ์ œ๋ฅผ ์ผ์œผํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

  1. ๋ชจ๋“ˆ ๊ทธ๋ž˜ํ”„ ๋ณต์žก์„ฑ ์ฆ๊ฐ€: ๋ฐฐ๋Ÿด ํŒŒ์ผ์ด ์ฆ๊ฐ€ํ• ์ˆ˜๋ก ๋ชจ๋“ˆ ๊ฐ„ ์˜์กด์„ฑ ๊ทธ๋ž˜ํ”„๊ฐ€ ๋ณต์žกํ•ด์ง‘๋‹ˆ๋‹ค.
  2. ๋ถˆํ•„์š”ํ•œ ๋ชจ๋“ˆ ๋กœ๋”ฉ: ํŠนํžˆ ์™€์ผ๋“œ์นด๋“œ ๋‚ด๋ณด๋‚ด๊ธฐ(export * from)๋ฅผ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ, ์‹ค์ œ๋กœ ํ•„์š”ํ•˜์ง€ ์•Š์€ ๋ชจ๋“ˆ๊นŒ์ง€ ๋กœ๋“œ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

๋ชจ๋“ˆ ๊ทธ๋ž˜ํ”„ ๋น„๊ต

๋ฐฐ๋Ÿด ํŒŒ์ผ ์—†์ด:

App.js
โ”œโ”€โ”€ Button.js
โ”œโ”€โ”€ TextField.js
โ””โ”€โ”€ Checkbox.js

 

๋ฐฐ๋Ÿด ํŒŒ์ผ ์‚ฌ์šฉ ์‹œ:

App.js
โ””โ”€โ”€ components/index.js
    โ”œโ”€โ”€ Button.js
    โ”œโ”€โ”€ TextField.js
    โ””โ”€โ”€ Checkbox.js

 

์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ ์˜ˆ์‹œ

๋ชจ๋“ˆ ์ˆ˜๋ฐฐ๋Ÿด ํŒŒ์ผ ์—†์Œ๋ฐฐ๋Ÿด ํŒŒ์ผ ์‚ฌ์šฉ์ฐจ์ด

๋ชจ๋“ˆ ์ˆ˜ ๋ฐฐ๋Ÿด ํŒŒ์ผ ์—†์Œ ๋ฐฐ๋Ÿด ํŒŒ์ผ ์‚ฌ์šฉ ์ฐจ์ด
10๊ฐœ 150ms 180ms +20%
50๊ฐœ 450ms 630ms +40%
100๊ฐœ 820ms 1250ms +52%

 

์ฐธ๊ณ : ์ด ์ˆ˜์น˜๋Š” ์˜ˆ์‹œ์šฉ์ด๋ฉฐ, ์‹ค์ œ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ๋‹ค์–‘ํ•œ ์š”์ธ์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

FSD ์•„ํ‚คํ…์ฒ˜์—์„œ์˜ ๋ฐฐ๋Ÿด ํŒŒ์ผ

Feature-Sliced Design(FSD)์€ ๋ฐฐ๋Ÿด ํŒŒ์ผ๊ณผ ์œ ์‚ฌํ•œ ํŒจํ„ด์„ ์˜๋„์ ์œผ๋กœ ํ™œ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๋‹จ์ˆœํžˆ ์ฝ”๋“œ ์ž‘์„ฑ์˜ ํŽธ์˜์„ฑ์„ ์œ„ํ•œ ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ์•„ํ‚คํ…์ฒ˜์˜ ๊ตฌ์กฐ์™€ ๋ชฉ์ ์„ ๋ฐ˜์˜ํ•œ ๊ฒฐ์ •์ž…๋‹ˆ๋‹ค.

FSD๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ธฐ๋Šฅ ๋‹จ์œ„๋กœ ๋ถ„ํ• ํ•˜๊ณ  ๊ณ„์ธตํ™”๋œ ๊ตฌ์กฐ๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ์„ ๋ชฉํ‘œ๋กœ ํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ตฌ์กฐ์—์„œ ๊ฐ ๋ ˆ์ด์–ด(layer)์™€ ์Šฌ๋ผ์ด์Šค(slice)๋Š” ๋ช…ํ™•ํ•œ ์ฑ…์ž„๊ณผ ์—ญํ• ์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค. ๋ฐฐ๋Ÿด ํŒŒ์ผ๊ณผ ์œ ์‚ฌํ•œ ํŒจํ„ด์„ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ, FSD๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ด์ ์„ ์–ป์Šต๋‹ˆ๋‹ค:

  • ์บก์Аํ™”: ๊ฐ ๋ชจ๋“ˆ์˜ ๋‚ด๋ถ€ ๊ตฌํ˜„์„ ์ˆจ๊ธฐ๊ณ  ๊ณต๊ฐœ API๋งŒ์„ ๋…ธ์ถœํ•ฉ๋‹ˆ๋‹ค.
  • ์˜์กด์„ฑ ๊ด€๋ฆฌ: ์ƒ์œ„ ๋ ˆ์ด์–ด๊ฐ€ ํ•˜์œ„ ๋ ˆ์ด์–ด์—๋งŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ์ œํ•œํ•ฉ๋‹ˆ๋‹ค.
  • ์žฌ์‚ฌ์šฉ์„ฑ: ๊ณตํ†ต ๊ธฐ๋Šฅ์„ ์‰ฝ๊ฒŒ ๊ณต์œ ํ•˜๊ณ  ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.
  • ์œ ์ง€๋ณด์ˆ˜์„ฑ: ์ฝ”๋“œ ๊ตฌ์กฐ๋ฅผ ์ผ๊ด€๋˜๊ฒŒ ์œ ์ง€ํ•˜์—ฌ ๊ฐœ๋ฐœ์ž๊ฐ€ ์‰ฝ๊ฒŒ ์ดํ•ดํ•˜๊ณ  ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ๊ตฌ์กฐ๋Š” ๋Œ€๊ทœ๋ชจ ํ”„๋กœ์ ํŠธ์—์„œ ์ฝ”๋“œ์˜ ์กฐ์งํ™”์™€ ๊ด€๋ฆฌ๋ฅผ ์šฉ์ดํ•˜๊ฒŒ ๋งŒ๋“ค์–ด, ์žฅ๊ธฐ์ ์œผ๋กœ ํ”„๋กœ์ ํŠธ์˜ ํ™•์žฅ์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ํ–ฅ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ FSD์—์„œ ๋ฐฐ๋Ÿด ํŒŒ์ผ๊ณผ ์œ ์‚ฌํ•œ ํŒจํ„ด์˜ ์‚ฌ์šฉ์€ ๋‹จ์ˆœํ•œ ํŽธ์˜์„ฑ์„ ๋„˜์–ด์„œ, ์•„ํ‚คํ…์ฒ˜์˜ ํ•ต์‹ฌ ์›์น™์„ ์‹คํ˜„ํ•˜๊ธฐ ์œ„ํ•œ ์ „๋žต์  ์„ ํƒ์ด๋ผ๊ณ  ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

FSD์—์„œ์˜ ๋ฐฐ๋Ÿด ํŒŒ์ผ ํ™œ์šฉ - ์‹ค์ œ ๊ฒฝํ—˜๊ณผ ํŒ

FSD ์•„ํ‚คํ…์ฒ˜๋ฅผ ์‹ค์ œ ํ”„๋กœ์ ํŠธ์— ์ ์šฉํ•ด๋ณธ ๊ฒฝํ—˜์— ๋”ฐ๋ฅด๋ฉด, ๋ฐฐ๋Ÿด ํŒŒ์ผ(Public API)์˜ ์ฃผ์š” ์ด์  ์ค‘ ํ•˜๋‚˜๋Š” ์œ ์ง€๋ณด์ˆ˜์„ฑ ํ–ฅ์ƒ์ž…๋‹ˆ๋‹ค. ์ด์— ๋Œ€ํ•ด ์ด๋„ˆ์„œํด ์žฌ์ง์ž ๊ณผ์ •์—์„œ ๋งŒ๋‚œ ๋™๋ฃŒ ๊ฐœ๋ฐœ์ž๋ถ„๊ป˜์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ธ์‚ฌ์ดํŠธ๋ฅผ ๊ณต์œ ํ•ด์ฃผ์…จ์–ด์š”:

"FSD๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด์„œ ๋А๋‚€ ๋ฐ”๋กœ๋Š”, Public API๋ฅผ ํ™œ์šฉํ•˜๋Š” ์ฃผ๋œ ์ด์œ  ์ค‘ ํ•˜๋‚˜๊ฐ€ ์œ ์ง€๋ณด์ˆ˜์— ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, entities/user์˜ ์Šคํ‚ค๋งˆ๊ฐ€ ์ผ๋ถ€ ๋ณ€๊ฒฝ๋˜๋”๋ผ๋„ features/loginUser, features/registerUser ๋“ฑ์—์„œ import ๊ตฌ๋ฌธ์„ ๋ณ€๊ฒฝํ•  ํ•„์š”๊ฐ€ ์—†์–ด์ง‘๋‹ˆ๋‹ค."


์ด๋Š” FSD์˜ ํ•ต์‹ฌ ์›์น™ ์ค‘ ํ•˜๋‚˜์ธ ๋ชจ๋“ˆ ๊ฐ„ ์˜์กด์„ฑ ๊ด€๋ฆฌ์™€ ์ผ์น˜ํ•˜๋Š” ๊ด€์ ์ž…๋‹ˆ๋‹ค.

 

๋ฐฐ๋Ÿด ํŒŒ์ผ ์ƒ์„ฑ ์ž๋™ํ™”

๋Œ€๊ทœ๋ชจ ํ”„๋กœ์ ํŠธ์—์„œ ๋งค๋ฒˆ ์ˆ˜๋™์œผ๋กœ ๋ฐฐ๋Ÿด ํŒŒ์ผ(Public API)์„ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์€ ๋ฒˆ๊ฑฐ๋กœ์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•œ ์‹ค์šฉ์ ์ธ ์ ‘๊ทผ ๋ฐฉ๋ฒ•์œผ๋กœ, ๋ฐฐ๋Ÿด ํŒŒ์ผ์„ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•˜๋Š” ์Šคํฌ๋ฆฝํŠธ๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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

pnpm api features/loginUser


์ด๋Ÿฌํ•œ CLI ๋ช…๋ น์–ด๋ฅผ ํ†ตํ•ด features/loginUser ๋‚ด๋ถ€์˜ ๋ชจ๋“ˆ์„ ์ž๋™์œผ๋กœ ๋ถ„์„ํ•˜๊ณ , ๋ช…์‹œ์ ์œผ๋กœ ๋‚ด๋ณด๋‚ด๋Š” ๋ฐฐ๋Ÿด ํŒŒ์ผ์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋•Œ _๋กœ ์‹œ์ž‘ํ•˜๋Š” ํŒŒ์ผ์ด๋‚˜ ํด๋”๋Š” ๋‚ด๋ถ€์šฉ์œผ๋กœ ๊ฐ„์ฃผํ•˜์—ฌ ์ž๋™์œผ๋กœ ์ œ์™ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

๋ช…์‹œ์  ๋‚ด๋ณด๋‚ด๊ธฐ์˜ ์ค‘์š”์„ฑ

์•ž์„œ ์–ธ๊ธ‰ํ–ˆ๋“ฏ์ด, ์™€์ผ๋“œ์นด๋“œ ๋‚ด๋ณด๋‚ด๊ธฐ(export * from './module')๋Š” ๊ถŒ์žฅ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋Œ€์‹ , ๋ช…์‹œ์  ๋‚ด๋ณด๋‚ด๊ธฐ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์„ฑ๋Šฅ ์ด์Šˆ๋ฅผ ์ตœ์†Œํ™”ํ•˜๋ฉด์„œ๋„ FSD์˜ ์ด์ ์„ ์ถฉ๋ถ„ํžˆ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

// Good: ๋ช…์‹œ์  ๋‚ด๋ณด๋‚ด๊ธฐ
export { LoginForm } from './LoginForm';
export { useAuth } from './useAuth';
export type { User } from './types';

์ด๋Ÿฌํ•œ ์ ‘๊ทผ ๋ฐฉ์‹์€ ๋ชจ๋“ˆ์˜ ๊ณต๊ฐœ API๋ฅผ ๋ช…ํ™•ํžˆ ์ •์˜ํ•˜๊ณ , ๋ถˆํ•„์š”ํ•œ ์˜์กด์„ฑ์„ ๋ฐฉ์ง€ํ•˜๋ฉฐ, ์ฝ”๋“œ์˜ ๊ฐ€๋…์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ํ–ฅ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค. ์ข€ ๋” ์ƒ์„ธํ•œ ์˜ˆ์‹œ๋ฅผ ๋“ค์–ด๋ณผ๊ฒŒ์š”.

์Šคํฌ๋ฆฝํŠธ์— ๋Œ€ํ•œ ํด๋” ๊ตฌ์กฐ ์˜ˆ์‹œ:

project-root/
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ features/
โ”‚   โ”‚   โ””โ”€โ”€ loginUser/
โ”‚   โ”‚       โ”œโ”€โ”€ ui/
โ”‚   โ”‚       โ”œโ”€โ”€ model/
โ”‚   โ”‚       โ””โ”€โ”€ index.ts (์ž๋™ ์ƒ์„ฑ๋  ํŒŒ์ผ)
โ”‚   โ””โ”€โ”€ ... (๋‹ค๋ฅธ FSD ํด๋”๋“ค)
โ”œโ”€โ”€ scripts/
โ”‚   โ””โ”€โ”€ generate-barrel.ts
โ”œโ”€โ”€ package.json
โ””โ”€โ”€ tsconfig.json

 

scripts/generate-barrel.ts ํŒŒ์ผ์˜ ๋‚ด์šฉ:

import fs from 'fs';
import path from 'path';

function generateBarrel(directory: string) {
  const files = fs.readdirSync(directory);
  const exports: string[] = [];

  files.forEach(file => {
    if (file.startsWith('_') || file === 'index.ts') return;

    const filePath = path.join(directory, file);
    const stats = fs.statSync(filePath);

    if (stats.isDirectory()) {
      exports.push(`export * from './${file}';`);
    } else if (file.endsWith('.ts') || file.endsWith('.tsx')) {
      const name = path.parse(file).name;
      exports.push(`export { ${name} } from './${name}';`);
    }
  });

  const content = exports.join('\n') + '\n';
  fs.writeFileSync(path.join(directory, 'index.ts'), content);
}

const targetDir = process.argv[2];
if (!targetDir) {
  console.error('Please specify a target directory');
  process.exit(1);
}

const fullPath = path.resolve(process.cwd(), targetDir);
generateBarrel(fullPath);
console.log(`Barrel file generated for ${fullPath}`);

 

package.json์— ์Šคํฌ๋ฆฝํŠธ ์ถ”๊ฐ€:

{
  "scripts": {
    "api": "ts-node scripts/generate-barrel.ts"
  }
}

์ด์ œ pnpm api features/loginUser ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜๋ฉด, src/features/loginUser/index.ts ํŒŒ์ผ์ด ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋˜๊ฑฐ๋‚˜ ์—…๋ฐ์ดํŠธ๋ฉ๋‹ˆ๋‹ค! ์•„๋ž˜๋ถ€ํ„ฐ๋Š” ์ข€ ๋” ์ผ๋ฐ˜์ ์ธ ์˜ˆ์‹œ๋ฅผ ๋“ค์–ด๋ณผ๊ฒŒ์š”.

 

FSD ๊ธฐ๋ณธ ํด๋” ๊ตฌ์กฐ:

src/
โ”œโ”€โ”€ app/        # ๊ธ€๋กœ๋ฒŒ ์„ค์ •, ์Šคํƒ€์ผ, ํ”„๋กœ๋ฐ”์ด๋”
โ”œโ”€โ”€ processes/  # ๋น„์ฆˆ๋‹ˆ์Šค ํ”„๋กœ์„ธ์Šค
โ”œโ”€โ”€ pages/      # ํŽ˜์ด์ง€ ์ปดํฌ๋„ŒํŠธ
โ”œโ”€โ”€ widgets/    # ๋ณตํ•ฉ UI ๋ธ”๋ก
โ”œโ”€โ”€ features/   # ์‚ฌ์šฉ์ž ์ธํ„ฐ๋ž™์…˜
โ”œโ”€โ”€ entities/   # ๋น„์ฆˆ๋‹ˆ์Šค ์—”ํ‹ฐํ‹ฐ
โ””โ”€โ”€ shared/     # ๊ณต์œ  ์œ ํ‹ธ๋ฆฌํ‹ฐ, UI ํ‚คํŠธ

 

FSD์—์„œ์˜ ๋ฐฐ๋Ÿด ํŒŒ์ผ ์‚ฌ์šฉ ์˜ˆ์‹œ

features/auth/index.ts (์ ์ ˆํ•œ ๋ฐฐ๋Ÿด ํŒŒ์ผ ์‚ฌ์šฉ):

// ๋ช…์‹œ์  ๋‚ด๋ณด๋‚ด๊ธฐ - ๊ณต๊ฐœ API๋ฅผ ๋ช…ํ™•ํžˆ ์ •์˜
export { LoginForm } from './ui/LoginForm';
export { useAuth } from './model/useAuth';
export type { AuthUser } from './model/types';

 

๊ท ํ˜• ์žกํžŒ ์ ‘๊ทผ๋ฒ•: ์‹ค์ œ ๊ตฌํ˜„ ์˜ˆ์‹œ

1. ๊ตฌ์กฐํ™”๋œ ๋ฐฐ๋Ÿด ํŒŒ์ผ ์‚ฌ์šฉ:

features/
โ”œโ”€โ”€ auth/
โ”‚   โ”œโ”€โ”€ ui/
โ”‚   โ”‚   โ”œโ”€โ”€ LoginForm.tsx
โ”‚   โ”‚   โ””โ”€โ”€ RegisterForm.tsx
โ”‚   โ”œโ”€โ”€ model/
โ”‚   โ”‚   โ”œโ”€โ”€ useAuth.ts
โ”‚   โ”‚   โ””โ”€โ”€ types.ts
โ”‚   โ””โ”€โ”€ index.ts  # ์•„ํ‚คํ…์ฒ˜ ๊ฒฝ๊ณ„์—๋งŒ ๋ฐฐ๋Ÿด ํŒŒ์ผ ์‚ฌ์šฉ
โ””โ”€โ”€ userProfile/
    โ”œโ”€โ”€ ui/
    โ”œโ”€โ”€ model/
    โ””โ”€โ”€ index.ts

 

2. ๋ช…์‹œ์  ๋‚ด๋ณด๋‚ด๊ธฐ:

// features/auth/index.ts
// ๋ช…์‹œ์  ๋‚ด๋ณด๋‚ด๊ธฐ๋กœ ๊ณต๊ฐœ API ์ •์˜
export { LoginForm } from './ui/LoginForm';
export { RegisterForm } from './ui/RegisterForm';
export { useAuth } from './model/useAuth';
export type { User, AuthCredentials } from './model/types';

 

3. FSD ์•„ํ‚คํ…์ฒ˜ ๊ทœ์น™์— ๋”ฐ๋ฅธ ๊ฐ€์ ธ์˜ค๊ธฐ:

// ์ž˜๋ชป๋œ ๋ฐฉ์‹: ๋ ˆ์ด์–ด ์šฐํšŒ
import { Button } from '@/shared/ui/Button';

// ์˜ฌ๋ฐ”๋ฅธ ๋ฐฉ์‹: ์•„ํ‚คํ…์ฒ˜ ๊ฒฝ๊ณ„ ์กด์ค‘
import { Button } from '@/shared/ui';

 

4. ์‹ค์ œ React ์ปดํฌ๋„ŒํŠธ ์˜ˆ์‹œ:

// features/auth/ui/LoginForm.tsx
import { useState } from 'react';
import { Button, TextField } from '@/shared/ui';
import { useAuth } from '../model/useAuth';
import type { AuthCredentials } from '../model/types';

export const LoginForm = () => {
  const [credentials, setCredentials] = useState({
    email: '',
    password: ''
  });
  const { login, isLoading } = useAuth();

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    login(credentials);
  };

  return (
    
      <TextField
        label="Email"
        value={credentials.email}
        onChange={(e) => setCredentials(prev => ({
          ...prev,
          email: e.target.value
        }))}
      />
      <TextField
        type="password"
        label="Password"
        value={credentials.password}
        onChange={(e) => setCredentials(prev => ({
          ...prev,
          password: e.target.value
        }))}
      />
      
        {isLoading ? 'Logging in...' : 'Login'}
      
    
  );
};

 

ํ˜„๋Œ€์  ๋„๊ตฌ์˜ ์˜ํ–ฅ

์ตœ์‹  ๋นŒ๋“œ ๋„๊ตฌ๋“ค์€ ๋ฐฐ๋Ÿด ํŒŒ์ผ์˜ ์„ฑ๋Šฅ ๋ฌธ์ œ๋ฅผ ์ผ๋ถ€ ์™„ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

 

๋ฒˆ๋“ค๋Ÿฌ๋ณ„ ์ตœ์ ํ™” ์„ค์ •:

Vite ์„ค์ • ์˜ˆ์‹œ:

// vite.config.js
export default {
  build: {
    target: 'esnext',
    minify: 'esbuild',
    rollupOptions: {
      output: {
        manualChunks(id) {
          // ์ตœ์ ํ™”๋œ ์ฒญํฌ ์„ค์ •
        }
      }
    }
  }
}

 

Webpack ์„ค์ • ์˜ˆ์‹œ:

// webpack.config.js
module.exports = {
  optimization: {
    usedExports: true, // ํŠธ๋ฆฌ ์‰์ดํ‚น ํ™œ์„ฑํ™”
    moduleIds: 'deterministic',
    splitChunks: {
      chunks: 'all',
    }
  }
}

 

์„ฑ๋Šฅ ์ธก์ • - ์‹ค์‹œ๊ฐ„ ํ”„๋กœ์ ํŠธ ๋ชจ๋‹ˆํ„ฐ๋ง

// build-analyzer.ts
import { performance } from 'perf_hooks';

export function measureBuildTime(taskName: string, task: () => void) {
  const startTime = performance.now();
  task();
  const endTime = performance.now();
  console.log(`Task "${taskName}" took ${endTime - startTime}ms`);
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
measureBuildTime('Module compilation', () => {
  // ๋นŒ๋“œ ๋กœ์ง
});

 

๊ฒฐ๋ก  - ํ”„๋กœ์ ํŠธ ๊ทœ๋ชจ๋ณ„ ๊ถŒ์žฅ ์ ‘๊ทผ๋ฒ•

 

์†Œ๊ทœ๋ชจ ํ”„๋กœ์ ํŠธ (10,000์ค„ ์ดํ•˜)

src/
โ”œโ”€โ”€ components/
โ”‚   โ””โ”€โ”€ index.ts  # ๋ฐฐ๋Ÿด ํŒŒ์ผ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
โ”œโ”€โ”€ hooks/
โ”‚   โ””โ”€โ”€ index.ts  # ๋ฐฐ๋Ÿด ํŒŒ์ผ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
โ””โ”€โ”€ utils/
    โ””โ”€โ”€ index.ts  # ๋ฐฐ๋Ÿด ํŒŒ์ผ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

 

์ค‘๊ฐ„ ๊ทœ๋ชจ ํ”„๋กœ์ ํŠธ (10,000~50,000์ค„)

src/
โ”œโ”€โ”€ features/
โ”‚   โ”œโ”€โ”€ feature1/
โ”‚   โ”‚   โ””โ”€โ”€ index.ts  # ์•„ํ‚คํ…์ฒ˜ ๊ฒฝ๊ณ„์—๋งŒ ๋ฐฐ๋Ÿด ํŒŒ์ผ
โ”‚   โ””โ”€โ”€ feature2/
โ”‚       โ””โ”€โ”€ index.ts
โ””โ”€โ”€ shared/
    โ”œโ”€โ”€ ui/
    โ”‚   โ””โ”€โ”€ index.ts  # ๋ช…์‹œ์  ๋‚ด๋ณด๋‚ด๊ธฐ ์‚ฌ์šฉ
    โ””โ”€โ”€ lib/
        โ””โ”€โ”€ index.ts  # ๋ช…์‹œ์  ๋‚ด๋ณด๋‚ด๊ธฐ ์‚ฌ์šฉ

 

๋Œ€๊ทœ๋ชจ ํ”„๋กœ์ ํŠธ (50,000์ค„ ์ด์ƒ)

src/
โ”œโ”€โ”€ app/
โ”œโ”€โ”€ processes/
โ”œโ”€โ”€ pages/
โ”‚   โ””โ”€โ”€ index.ts  # ์—„๊ฒฉํ•˜๊ฒŒ ์ œํ•œ๋œ ๋ฐฐ๋Ÿด ํŒŒ์ผ
โ”œโ”€โ”€ widgets/
โ”‚   โ””โ”€โ”€ index.ts  # ์—„๊ฒฉํ•˜๊ฒŒ ์ œํ•œ๋œ ๋ฐฐ๋Ÿด ํŒŒ์ผ
โ”œโ”€โ”€ features/
โ”‚   โ””โ”€โ”€ [๊ฐ ๊ธฐ๋Šฅ๋ณ„]/
โ”‚       โ””โ”€โ”€ index.ts  # ๊ณต๊ฐœ API๋งŒ ๋ช…์‹œ์  ๋‚ด๋ณด๋‚ด๊ธฐ
โ”œโ”€โ”€ entities/
โ”‚   โ””โ”€โ”€ [๊ฐ ์—”ํ‹ฐํ‹ฐ๋ณ„]/
โ”‚       โ””โ”€โ”€ index.ts  # ๊ณต๊ฐœ API๋งŒ ๋ช…์‹œ์  ๋‚ด๋ณด๋‚ด๊ธฐ
โ””โ”€โ”€ shared/
    โ””โ”€โ”€ [๊ฐ ๋ชจ๋“ˆ๋ณ„]/
        โ””โ”€โ”€ index.ts  # ์„ฑ๋Šฅ ์ธก์ • ํ›„ ๊ฒฐ์ •
 

 

๋ฐฐ๋Ÿด ํŒŒ์ผ์˜ ์‚ฌ์šฉ์€ ๊ฒฐ๊ตญ ๊ฐœ๋ฐœ ๊ฒฝํ—˜๊ณผ ์„ฑ๋Šฅ ์‚ฌ์ด์˜ ๊ท ํ˜•์„ ์ฐพ๋Š” ๋ฌธ์ œ๋ผ ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

FSD ์•„ํ‚คํ…์ฒ˜๋ฅผ ๋„์ž…ํ•œ๋‹ค๋ฉด, ๋ฐฐ๋Ÿด ํŒŒ์ผ์˜ ์žฅ๋‹จ์ ์„ ์ธ์‹ํ•˜๊ณ  ํ”„๋กœ์ ํŠธ์˜ ๊ทœ๋ชจ์™€ ํŒ€์˜ ํ•„์š”์— ๋งž๊ฒŒ ์ ์ ˆํžˆ ์กฐ์ ˆํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•  ๊ฒƒ ๊ฐ™์•„์š”.

๋ช…ํ™•ํ•œ ์•„ํ‚คํ…์ฒ˜ ๊ฒฝ๊ณ„, ๋ช…์‹œ์ ์ธ ๋‚ด๋ณด๋‚ด๊ธฐ, ๊ทธ๋ฆฌ๊ณ  ํ˜„๋Œ€์ ์ธ ๋นŒ๋“œ ๋„๊ตฌ๋ฅผ ํ™œ์šฉํ•œ๋‹ค๋ฉด, ๋ฐฐ๋Ÿด ํŒŒ์ผ์˜ ์ด์ ์„ ์ทจํ•˜๋ฉด์„œ๋„ ์„ฑ๋Šฅ ๋ฌธ์ œ๋ฅผ ์ตœ์†Œํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!

์™œ ์ธํ”„๋ผ์™€ ๋ฐ๋ธŒ์˜ต์Šค๋ฅผ ๊ณต๋ถ€ํ•˜๊ฒŒ ๋˜์—ˆ๋‚˜์š”? ๐Ÿค”

์•ˆ๋…•ํ•˜์„ธ์š”, ์˜ค๋Š˜์€ ์ธํ”„๋ผ์™€ ๋ฐ๋ธŒ์˜ต์Šค์— ๊ด€ํ•œ ์ด์•ผ๊ธฐ๋ฅผ ์ ์–ด๋ณด๋ ค ํ•ฉ๋‹ˆ๋‹ค. (์—ฐํœด๋ผ ์˜ค๋žœ๋งŒ์— ์ ์–ด๋ด…๋‹ˆ๋‹ค! ์‚ผ์ผ์ ˆ ๋งŒ์„ธ!)

ํ˜„์žฌ ์ €๋Š” ํšŒ์‚ฌ์—์„œ ๋„์ปค์™€ ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค ํ™˜๊ฒฝ์—์„œ ์ž‘์—… ์ค‘์ด์—์š”. ํŒ€์›๋“ค๊ณผ ์›ํ™œํ•œ ์†Œํ†ต์„ ์œ„ํ•ด ์Šคํ„ฐ๋””์™€ ๊ฐ™์€ ๋‹ค์–‘ํ•œ ํ•™์Šต์„ ์‹œ์ž‘ํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

 

ํ•˜์ง€๋งŒ ๋‹จ์ˆœํžˆ ๊ฐœ๋…๋งŒ ๊ณต๋ถ€ํ•˜๋Š” ๊ฒƒ์€ ์•„์‰ฌ์› ์–ด์š”.

๊ทธ๋ž˜์„œ ์‹ค์ œ๋กœ ์ธํ”„๋ผ๋ฅผ ์šด์˜ํ•˜๋Š” ๋ฐ๋ธŒ์˜ต์Šค ํŒ€๊ณผ์˜ ํ˜‘์—…์ด๋ผ๋Š” ๊ด€์ ์—์„œ ์ •๋ฆฌํ•ด๋ณด๊ณ  ์‹ถ์—ˆ์Šต๋‹ˆ๋‹ค.

 

๋˜ํ•œ ์ œ๊ฐ€ ๊ฐœ์ธ์ ์œผ๋กœ ์šด์˜ ์ค‘์ธ ์ƒ์šฉ ํ”„๋กœ์ ํŠธ์—์„œ๋„ ์ˆ˜์˜์‚ฌ๋ถ„๋“ค์˜ ์šด์˜๋น„์šฉ ์ ˆ๊ฐ์„ ์œ„ํ•ด ์ธํ”„๋ผ ํ•™์Šต์ด ํฐ ๋„์›€์ด ๋  ๊ฒƒ์ด๋ผ๊ณ  ํŒ๋‹จํ–ˆ์–ด์š”. ์ด๋Ÿฐ ์‹ค์งˆ์ ์ธ ํ•„์š”์„ฑ์ด ๋” ๊นŠ์€ ํ•™์Šต์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋งŒ๋“ค์–ด์ค๋‹ˆ๋‹ค.

์ด ๊ธ€์€ ํ˜„์žฌ ์ œ๊ฒŒ ํ•„์š”ํ•œ ๋‚ด์šฉ๊ณผ ์ œ ์ฃผ๊ด€์ด ๋“ค์–ด๊ฐ„ ์ •๋ฆฌ๋ณธ์ด๋‹ˆ, ์ฝ์œผ์‹œ๋Š” ๋ถ„๋“ค๋„ ๊ผญ ๊ต์ฐจ๊ฒ€์ฆ์ด๋‚˜ ์ถ”๊ฐ€์ ์ธ ํ•™์Šต์„ ์ง„ํ–‰ํ•˜์‹œ๋Š” ๊ฒƒ์„ ์ถ”์ฒœ๋“œ๋ ค์š”!


์ธํ”„๋ผ ์ง€์‹์˜ ์‹ค๋ฌด์  ๊ฐ€์น˜ ๐ŸŒ‰

  • ์ธํ”„๋ผ๋ฅผ ์ดํ•ดํ•˜๋ฉด ๊ฐœ๋ฐœ ํ™˜๊ฒฝ๊ณผ ๋ฐฐํฌ ๊ณผ์ •์„ ์™„๋ฒฝํ•˜๊ฒŒ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์–ด์š”.
  • ํด๋ผ์šฐ๋“œ ์ธํ”„๋ผ์— ๋Šฅ์ˆ™ํ•ด์ง€๋ฉด ๋ฆฌ์†Œ์Šค ํ™•์žฅ๊ณผ ๋น„์šฉ ์ตœ์ ํ™”๋ฅผ ์†์‰ฝ๊ฒŒ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด์š”.
  • ๋„์ปค & ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค๋ฅผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋‹ค๋ฃจ๋ฉด ์–ด๋–ค ํ™˜๊ฒฝ์—์„œ๋„ ์ผ๊ด€๋œ ๊ฐœ๋ฐœ๊ณผ ์‹ ์†ํ•œ ๋ฐฐํฌ๊ฐ€ ๊ฐ€๋Šฅํ•ด์ ธ ์ž‘์—… ํšจ์œจ์ด ํฌ๊ฒŒ ํ–ฅ์ƒ๋ผ์š”!

๋ฐ๋ธŒ์˜ต์Šค ์ฒ ํ•™ ๐Ÿ’ญ

  • ๋ฐ๋ธŒ์˜ต์Šค ์ฒ ํ•™์—์„œ๋Š” ๊ฐœ๋ฐœ์ด ์šด์˜๊นŒ์ง€ ํ•˜๋Š” ๊ฒƒ์„ ์›์น™์œผ๋กœ ํ•ด์š”.
  • ๊ฐœ๋ฐœ์ž๊ฐ€ ์šด์˜ํ™˜๊ฒฝ๊นŒ์ง€ ๊ด€์—ฌํ•˜๋Š” ๊ฒƒ์„ ๋‹ค๋ฃจ๋Š” ๊ฒƒ์ด ๋ฐ๋ธŒ์˜ต์Šค ์ฒ ํ•™์ด์—์š”.
  • ์ด๋•Œ ๋งŽ์ด ๋‹ค๋ฃจ๋Š” ๊ธฐ์ˆ ์…‹ ์ค‘ ํ•˜๋‚˜๊ฐ€ ๋„์ปค์™€ ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค์˜ˆ์š”.

๋„์ปค ์—ญ๋Ÿ‰์€ ์–ด๋А์ •๋„๊ฐ€ ํ•„์š”ํ• ๊นŒ์š”? ๐Ÿณ

  • ๊ฐœ๋ฐœ์ž๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ Docker ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŒจํ‚ค์ง•, ๋ฐฐํฌ, ์‹คํ–‰ํ•˜๋Š” ์ „์ฒด ๊ณผ์ •์ธ ๋„์ปค๋ผ์ด์ง•์„ ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•ด์š”.
  • ์ปจํ…Œ์ด๋„ˆ ์ƒํ™ฉ์—์„œ์˜ ์ตœ์ ํ™”์™€ ์ผ๋ฐ˜์ ์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ฐฐํฌ ์ƒํ™ฉ์—์„œ์˜ ์ตœ์ ํ™” ๋ฐฉ๋ฒ•์ด ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ์ด์—์š”.
    • ์˜ˆ: ์ปจํ…Œ์ด๋„ˆ ๋‚ด๋ถ€๋Š” ๋ถˆ๋ณ€์„ฑ(immutability)์„ ๊ฐ€์ง€๋ฏ€๋กœ ๋กœ๊ทธ๋ฅผ ์–ด๋–ป๊ฒŒ ๊ด€๋ฆฌํ• ์ง€ ๊ณ ๋ฏผํ•ด์•ผ ํ•ด์š”.
    • ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€ ํฌ๊ธฐ ์ตœ์†Œํ™”: ๋ถˆํ•„์š”ํ•œ ํŒŒ์ผ๊ณผ ์ข…์†์„ฑ์„ ์ œ๊ฑฐํ•˜์—ฌ ์ด๋ฏธ์ง€ ํฌ๊ธฐ๋ฅผ ์ค„์—ฌ์•ผ ํ•ด์š”.
    • ์ปจํ…Œ์ด๋„ˆ ๋‚ด๋ถ€์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์™ธ๋ถ€๋กœ ๋งํฌ๋ฅผ ๊ฑธ์–ด์ค„ ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๊ธฐ๋Šฅ(๋ณผ๋ฅจ)์„ ํ†ตํ•ด ์˜์†์  ๋ฐ์ดํ„ฐ๋ฅผ ๊ด€๋ฆฌํ•ด์•ผ ํ•ด์š”.
  • ๋„์ปค๋กœ ๊ฐ”์„ ๋•Œ ์–ด๋–ค ํŠน์„ฑ์ด ์žˆ๊ณ  ์–ด๋–ค ์‹์œผ๋กœ ์ตœ์ ํ™”ํ•ด์•ผ ํ• ์ง€ ์ด๋Ÿฐ ๊ฒƒ๋“ค์„ ์„ค๊ณ„ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•ด์š”.
    • ์ด๋ฏธ์ง€ ๋ ˆ์ด์–ด ์ตœ์ ํ™” (์บ์‹ฑ ํ™œ์šฉ)
    • ๋ฉ€ํ‹ฐ ์Šคํ…Œ์ด์ง€ ๋นŒ๋“œ ์‚ฌ์šฉ: ํ•˜๋‚˜์˜ Dockerfile์—์„œ ์—ฌ๋Ÿฌ ๋‹จ๊ณ„๋ฅผ ๊ฑฐ์ณ ์ตœ์ข… ์ด๋ฏธ์ง€๋ฅผ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ, ๋นŒ๋“œ ๋„๊ตฌ๋‚˜ ์†Œ์Šค ์ฝ”๋“œ ์—†์ด ์‹คํ–‰ ํŒŒ์ผ๋งŒ ํฌํ•จ๋œ ์ž‘์€ ํฌ๊ธฐ์˜ ์ตœ์ข… ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•ด์š”.
    • ๋น„๋ฃจํŠธ ์‚ฌ์šฉ์ž๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰ํ•˜๊ธฐ
    • ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ ํ†ตํ•œ ์„ค์ • ๊ด€๋ฆฌ

์•„ํ‚คํ…์ฒ˜ ์„ค๊ณ„ ๋ฐฉ๋ฒ•์€? ๐Ÿ—๏ธ

  • ์ผ๋ฐ˜์ ์œผ๋กœ ๋‹จ์ˆœํžˆ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋ฐฐํฌํ•ด์„œ ์„œ๋น„์Šคํ•  ๋•Œ๋Š” ๋ชจ๋†€๋ฆฌ์‹(Monolithic) ์ƒํƒœ๋กœ ๋ฐฐํฌํ•ด์•ผ ํ•  ๋•Œ๊ฐ€ ์žˆ์–ด์š”.
  • ๋„์ปค๋‚˜ ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค๋ฅผ ๋‹ค๋ฃจ๋‹ค ๋ณด๋ฉด ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์ƒํƒœ๋กœ ์„ค๊ณ„ํ•˜๊ณ  ์ชผ๊ฐœ์•ผ ํ•  ๋•Œ๊ฐ€ ๋งŽ์•„์š”.
  • ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค์˜ ํŠน์„ฑ์„ ํ™œ์šฉํ•˜๋Š” ๊ฒฝ์šฐ๋„ ๋งŽ์•„์š”.
    • ์„œ๋น„์Šค ๋ถ„๋ฆฌ์™€ ๋…๋ฆฝ์  ์Šค์ผ€์ผ๋ง
    • ์žฅ์•  ๊ฒฉ๋ฆฌ(Fault Isolation)
    • ๊ธฐ๋Šฅ๋ณ„ ๋ฐฐํฌ ๋ฐ ๋กค๋ฐฑ ์šฉ์ด์„ฑ
  • ์˜ˆ์‹œ)
    • ๋ชจ๋†€๋ฆฌ์‹ ์•„ํ‚คํ…์ฒ˜๋Š” ์ „์ฒด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ํ•˜๋‚˜์˜ ํฐ ์ฝ”๋“œ๋ฒ ์ด์Šค๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ์–ด ๊ฐœ๋ฐœ, ํ…Œ์ŠคํŠธ, ๋ฐฐํฌ๊ฐ€ ์ƒ๋Œ€์ ์œผ๋กœ ๊ฐ„๋‹จํ•ด์š”.
    • ๊ทธ๋Ÿฌ๋‚˜ ์•„๋ž˜์™€๊ฐ™์€ ์ด์œ ๋กœ ๋„์ปค๋‚˜ ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค์™€ ๊ฐ™์€ ์ปจํ…Œ์ด๋„ˆ ๊ธฐ์ˆ ์„ ๋‹ค๋ฃจ๋‹ค ๋ณด๋ฉด, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜๋กœ ์„ค๊ณ„ํ•˜๊ณ  ๋ถ„ํ• ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•„์ ธ์š”.
      • ๋…๋ฆฝ์ ์ธ ๋ฐฐํฌ: ๊ฐ ์„œ๋น„์Šค๋ฅผ ๊ฐœ๋ณ„์ ์œผ๋กœ ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ์–ด, ์ „์ฒด ์‹œ์Šคํ…œ์„ ์ค‘๋‹จํ•˜์ง€ ์•Š๊ณ ๋„ ํŠน์ • ๊ธฐ๋Šฅ์„ ์—…๋ฐ์ดํŠธํ•˜๊ฑฐ๋‚˜ ํ™•์žฅํ•  ์ˆ˜ ์žˆ์–ด์š” (API ์„œ๋ฒ„, ํ”„๋ก ํŠธ์—”๋“œ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋“ฑ์œผ๋กœ ๋ถ„๋ฆฌํ•˜์—ฌ ๊ฐ๊ฐ์„ ๋…๋ฆฝ์ ์ธ ์ปจํ…Œ์ด๋„ˆ๋กœ ์šด์˜).
      • ๋ฆฌ์†Œ์Šค ํšจ์œจ์„ฑ: ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค์˜ ํŠน์„ฑ์ƒ ๊ฐ ์„œ๋น„์Šค์— ํ•„์š”ํ•œ ๋ฆฌ์†Œ์Šค๋ฅผ ๊ฐœ๋ณ„์ ์œผ๋กœ ํ• ๋‹นํ•˜๊ณ  ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด, ๋ฉ”๋ชจ๋ฆฌ์™€ CPU ์‚ฌ์šฉ์„ ์ตœ์ ํ™”ํ•  ์ˆ˜ ์žˆ์–ด์š”.
      • ๊ธฐ์ˆ  ๋‹ค์–‘์„ฑ: ๊ฐ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค๋Š” ๋…๋ฆฝ์ ์ด๋ฏ€๋กœ, ์„œ๋น„์Šค๋ณ„๋กœ ๊ฐ€์žฅ ์ ํ•ฉํ•œ ๊ธฐ์ˆ  ์Šคํƒ์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ์–ด์š”.

ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž๋„ ๋ฐฐํฌ์— ๊ด€์‹ฌ์„ ๊ฐ€์ ธ์•ผ ํ•ด์š”! ๐ŸŒ

๋‹น์žฅ ๋„์ปค๊นŒ์ง„ ์•„๋‹ˆ๋”๋ผ๋„, React.js๋ฅผ ๋‹ค๋ฃจ๋Š” ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž๋ถ„๋“ค ์ค‘์— ๋ฐฐํฌ๋ฅผ ์™„์ „ํžˆ ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ์ž๋‚˜ ์ธํ”„๋ผํŒ€์— ๋„˜๊ธฐ๊ณ  ๊ด€์‹ฌ์„ ๋‘์ง€ ์•Š๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์–ด์š”. ํ•˜์ง€๋งŒ ์ด๋Ÿฌ๋ฉด ๋งŽ์ด ์•„์‰ฝ์ž–์•„์š”?!

์•„๋ž˜์— ์ œ ๊ธฐ์ค€์„ ์ ์–ด๋†จ์–ด์š”.

  • FE๋„ ๋ฐฐํฌ๋ฅผ ํด๋ผ์šฐ๋“œ์ชฝ์— ํ•˜๋‹ค๋ณด๋‹ˆ ํด๋ผ์šฐ๋“œ ๊ธฐ์ˆ ๋“ค์„ ๋งŽ์ด ์•Œ๊ณ  ์žˆ์–ด์•ผ ํ•ด์š”.
  • ์ด๋ฅผํ…Œ๋ฉด FE์ชฝ์—์„œ ๋‹ค๋ฃจ๋‹ค๋ณด๋ฉด Node.js๋กœ ๊ฐœ๋ฐœํ•˜๊ณ  ์„œ๋น™์„ ํ•˜๊ฒŒ ๋ผ์š”.
  • ์‹ค์ œ๋กœ ํด๋ผ์šฐ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉด 'CFS3'๋ผ๊ณ  ๋งŽ์ด ์ด์•ผ๊ธฐ๋ฅผ ํ•˜๋Š”๋ฐ, ์ด๋Š” CloudFront ๋”ํ•˜๊ธฐ S3๋กœ ์Šคํƒœํ‹ฑํ•˜๊ฒŒ ํŒŒ์ผ์„ ๋นŒ๋“œํ•ด์„œ ์„œ๋น™ํ•œ๋‹ค๋Š” ๋œป์ด์—์š”.
  • ์ด๋Ÿฐ ๊ฒƒ๋“ค์„ ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋ฐ๋ธŒ์˜ต์ŠคํŒ€๊ณผ ์ด์•ผ๊ธฐ๋ฅผ ํ•ด์•ผ ํ•˜๊ณ , ์ ์–ด๋„ ๋‚ด๊ฐ€ ๊ฐœ๋ฐœํ•˜๋Š” ์‹œ์Šคํ…œ์ด ์–ด๋–ค ํ˜•์ƒ์œผ๋กœ ๋ฐฐํฌ๋˜์–ด ์„œ๋น™๋  ๊ฒƒ์ธ์ง€ ๊ทธ ๊ตฌ์กฐ๋ฅผ ์•Œ์•„์•ผ ์ธํ”„๋ผํŒ€์ด๋‚˜ ๋ฐ๋ธŒ์˜ต์ŠคํŒ€, ํ˜น์€ ํด๋ผ์šฐ๋“œ ์—”์ง€๋‹ˆ์–ด์™€์˜ ๋Œ€ํ™”๊ฐ€ ํŽธํ•˜์‹ค ๊ฑฐ์˜ˆ์š”.

๊ทธ๋Ÿฌ๋‹ˆ ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž๋ถ„๋“ค๋„ ๋ฐฐํฌ ๊ณผ์ •๊ณผ ์ธํ”„๋ผ์— ๋Œ€ํ•ด ์•Œ๊ณ  ๊ณ„์…”์•ผ ์ข‹์Šต๋‹ˆ๋‹ค!!

๋ฐ๋ธŒ์˜ต์Šค ์—”์ง€๋‹ˆ์–ด์™€์˜ ํ˜‘์—…์€ ์–ด๋–ป๊ฒŒ ์ด๋ฃจ์–ด์งˆ๊นŒ์š”? ๐Ÿค

  • ๋ฐ๋ธŒ์˜ต์Šค ์—”์ง€๋‹ˆ์–ด๋Š” ์„œ๋น„์Šค๋ฅผ ์˜คํ”ˆํ•˜๊ธฐ ์ „์ด๋‚˜ ์ธํ”„๋ผ์— ๋ฐฐํฌํ•˜๊ธฐ ์ „์— ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค์— ๋ฐฐํฌํ•˜๊ธฐ ์œ„ํ•ด ๋ช‡ ๊ฐ€์ง€ ์งˆ๋ฌธ์„ ํ•˜๊ฒŒ ๋ผ์š” (์ด๋•Œ ์ž˜ ๋Œ€๋‹ตํ•˜๊ธฐ ์œ„ํ•ด ์ง€๊ธˆ๋ถ€ํ„ฐ ์„ค๋ช…ํ•  ๋‚ด์šฉ๋“ค์„ ํ•™์Šตํ•ด๋ณด์•„์š”).
  • ์ด๋•Œ ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค๋‚˜ ๋„์ปค ๊ฐ™์€ ๊ธฐ๋ฐ˜๊ธฐ์ˆ ์„ ํญ๋„“๊ฒŒ ์•Œ๊ณ  ์žˆ์ง€ ์•Š์œผ๋ฉด ๋ฌธ์ œ๊ฐ€ ๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•„์š”.
    • ๋ฆฌ์†Œ์Šค ์š”๊ตฌ์‚ฌํ•ญ(CPU, ๋ฉ”๋ชจ๋ฆฌ)
    • ๋„คํŠธ์›Œํฌ ์ •์ฑ… ๋ฐ ์š”๊ตฌ์‚ฌํ•ญ
    • ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๋ฐ ๊ตฌ์„ฑ ๊ด€๋ฆฌ ๋ฐฉ๋ฒ•
    • ์ƒํƒœ ๊ด€๋ฆฌ ์ „๋žต

๊ทธ๋Ÿผ ํ˜‘์—…์„ ์œ„ํ•ด ์•Œ์•„์•ผ ํ•  ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค ์ด๋ก ์€ ๋ฌด์—‡์ด ์žˆ์„๊นŒ์š”? โ˜ธ๏ธ

  • ์šด์˜ ์ธก๋ฉด์—์„œ ํ•„์š”ํ•œ ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค ์ด๋ก ์ด ์žˆ๊ณ  ๊ฐœ๋ฐœ์ด๋‚˜ ๋ฐฐํฌ ์ธก๋ฉด์—์„œ ํ•„์š”ํ•œ ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค ์ด๋ก ์ด ์žˆ์–ด์š”.
  • ๊ฐœ๋ฐœ์ž๋Š” ๊ฐœ๋ฐœ์ด๋‚˜ ๋ฐฐํฌ ์ธก๋ฉด์—์„œ ํ•„์š”ํ•œ ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค ์ด๋ก ์„ ๋งŽ์ด ๋‹ค๋ฃจ๋Š” ๊ฒƒ์ด ์ข‹์•„์š”.

๊ฐœ๋ฐœ์ž๊ฐ€ ์•Œ์•„์•ผ ํ•  ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค ๋ฆฌ์†Œ์Šค ๐Ÿ“ฆ

  • ๊ฐœ๋ฐœ์ด๋‚˜ ๋ฐฐํฌ ์ธก๋ฉด์—์„œ ๋‹ค๋ฃจ๋Š” ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค ๋ฆฌ์†Œ์Šค๋“ค์€:
    • ํŒŒ๋“œ(Pod): ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค์˜ ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ๋ฐฐํฌ ๋‹จ์œ„์˜ˆ์š” (์›๋ž˜ ํ‘œ๊ธฐ๋Š” Pod์ด๋ฉฐ ํ•œ๊ธ€๋กœ๋Š” 'ํŒŒ๋“œ'๋กœ ํ‘œ๊ธฐํ•ด์š”).
    • ์„œ๋น„์Šค(Service): ํŒŒ๋“œ ์ง‘ํ•ฉ์— ๋Œ€ํ•œ ๋‹จ์ผ ์ง„์ž…์ ์„ ์ œ๊ณตํ•˜๋Š” ์ถ”์ƒํ™” ๊ณ„์ธต์ด์—์š”.
      • ์„œ๋น„์Šค๋Š” ์—ฌ๋Ÿฌ ํŒŒ๋“œ์— ๊ฑธ์ณ ํŠธ๋ž˜ํ”ฝ์„ ๋กœ๋“œ๋ฐธ๋Ÿฐ์‹ฑ ํ•ด์ฃผ๋Š” ์—ญํ• ์„ ํ•ด์š”.
      • ClusterIP, NodePort, LoadBalancer, ExternalName ๋“ฑ ๋‹ค์–‘ํ•œ ์œ ํ˜•์ด ์žˆ์–ด์š”.
      • ์„œ๋น„์Šค ๋””์Šค์ปค๋ฒ„๋ฆฌ์™€ ๋‚ด๋ถ€ DNS๋ฅผ ํ†ตํ•ด ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ๊ฐ„ ํ†ต์‹ ์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ด์š”.
      • ํŒŒ๋“œ์˜ ์ƒ์„ฑ, ์‚ญ์ œ, ์žฌ์‹œ์ž‘์— ๊ด€๊ณ„์—†์ด ์•ˆ์ •์ ์ธ ์—”๋“œํฌ์ธํŠธ๋ฅผ ์ œ๊ณตํ•ด์š”.
      • ๋ ˆ์ด๋ธ” ์…€๋ ‰ํ„ฐ๋ฅผ ํ†ตํ•ด ์–ด๋–ค ํŒŒ๋“œ๊ฐ€ ์„œ๋น„์Šค์— ํฌํ•จ๋ ์ง€ ๊ฒฐ์ •ํ•ด์š”.
    • ์ธ๊ทธ๋ ˆ์Šค(Ingress): ํด๋Ÿฌ์Šคํ„ฐ ์™ธ๋ถ€์—์„œ ๋‚ด๋ถ€ ์„œ๋น„์Šค๋กœ์˜ HTTP/HTTPS ๋ผ์šฐํŒ… ๊ทœ์น™ ๊ด€๋ฆฌ์˜ˆ์š”.
    • ์ธ๊ทธ๋ ˆ์Šค ์ปจํŠธ๋กค๋Ÿฌ(Ingress Controller): ์ธ๊ทธ๋ ˆ์Šค ๊ทœ์น™์„ ์‹ค์ œ๋กœ ๊ตฌํ˜„ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ์˜ˆ์š”.
  • ์ปจํŠธ๋กค ํ”Œ๋ ˆ์ธ ์˜์—ญ์€ ์•„์ง ๊นŠ๊ฒŒ ์•Œ ํ•„์š” ์—†๋‹ค๊ณ  ํŒ๋‹จํ–ˆ์–ด์š”. ๊ธฐ๋ณธ์ ์ธ ๊ฒƒ๋งŒ ์•Œ๋ฉด ๋ฉ๋‹ˆ๋‹ค!
    • ๊ธฐ๋ณธ์ ์ธ๊ฒƒ์˜ ์˜ˆ์‹œ:
      • kube-apiserver๊ฐ€ ๋ฌด์Šจ ์ผ์„ ํ•˜๋Š”์ง€, kube-dns(CoreDNS)๊ฐ€ ๋ฌด์Šจ ์ผ์„ ํ•˜๋Š”์ง€ ์ •๋„์˜ ๊ธฐ๋ณธ ๊ฐœ๋…๋งŒ ์•Œ๋ฉด ๋ผ์š”.

๋ฐ๋ธŒ์˜ต์Šค/์ธํ”„๋ผ ์ „๋ฌธ๊ฐ€๊ฐ€ ์•Œ์•„์•ผ ํ•  ๊ฒƒ ๐Ÿ”

  • ์ธํ”„๋ผ๋ฅผ ์ „๋ฌธ์ ์œผ๋กœ ๋‹ค๋ฃจ๊ฑฐ๋‚˜ ๋ฐ๋ธŒ์˜ต์Šค๋ฅผ ํ•˜๊ฒŒ ๋˜๋ฉด ์ปจํŠธ๋กค ํ”Œ๋ ˆ์ธ ์˜์—ญ์—์„œ ๋‹ค๋ฃจ๋Š” 4๋Œ€ ์ปดํฌ๋„ŒํŠธ์— ๋Œ€ํ•ด ์ƒ์„ธํžˆ ์•Œ์•„์•ผ ํ•ด์š”.
    • kube-apiserver: API ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ํ”„๋ก ํŠธ์—”๋“œ์˜ˆ์š”.
    • etcd: ํด๋Ÿฌ์Šคํ„ฐ ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜๋Š” ํ‚ค-๊ฐ’ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ˆ์š”.
    • kube-scheduler: ํŒŒ๋“œ๋ฅผ ๋…ธ๋“œ์— ํ• ๋‹นํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ์˜ˆ์š”.
    • kube-controller-manager: ๋‹ค์–‘ํ•œ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ์˜ˆ์š”.
  • IRSA(IAM Roles for Service Accounts) ๊ฐ™์€ ์ธ์ฆ ๋ฐฉ์‹์— ๋Œ€ํ•ด์„œ๋„ ์ž˜ ์•Œ์•„์•ผ ํ•ด์š”.
    • ์„œ๋น„์Šค ๊ณ„์ •์— ๋Œ€ํ•œ IAM ์—ญํ• ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•œ ๋ณด์•ˆ ์ธ์ฆ ์ •๋ณด ์ฒด์ธ์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹์ด์—์š”.
  • GKE(Google Kubernetes Engine), AWS์˜ EKS(Elastic Kubernetes Service) ๋“ฑ ๊ฐ ํด๋ผ์šฐ๋“œ์—์„œ ์ œ๊ณตํ•˜๋Š” ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค ํ™˜๊ฒฝ๋“ค์˜ ํŠน์„ฑ์— ๋Œ€ํ•ด์„œ๋„ ์ž˜ ์•Œ์•„์•ผ ํ•ด์š”.

๋ฆฌ์†Œ์Šค ์„ค๊ณ„์™€ ๊ตฌ์„ฑ ๊ฒฐ์ • ๐Ÿ“

ํ˜„์žฌ ํ™˜๊ฒฝ

  • ์šฐ๋ฆฌ๋Š” ํ˜„์žฌ OKD(OpenShift Kubernetes Distribution)๋ผ๋Š” ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค ๋ฐฐํฌํŒ์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์–ด์š”.
  • OKD๋Š” Red Hat์˜ ์˜คํ”ˆ์†Œ์Šค ์—”ํ„ฐํ”„๋ผ์ด์ฆˆ ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค ํ”Œ๋žซํผ์œผ๋กœ, ๊ธฐ๋ณธ์ ์ธ ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค ๊ธฐ๋Šฅ ์™ธ์—๋„ ๋‹ค์–‘ํ•œ ์ถ”๊ฐ€ ๊ธฐ๋Šฅ๋“ค์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

OKD ๋‚ด๋ถ€ ๋ฆฌ์†Œ์Šค ์„ค๊ณ„

  • OKD ํ™˜๊ฒฝ์—์„œ ๋ฆฌ์†Œ์Šค ๊ตฌ์„ฑ์„ ์–ด๋–ป๊ฒŒ ํ• ์ง€ ์„ค๊ณ„ํ•˜์—ฌ ๋ฐ๋ธŒ์˜ต์Šค ํŒ€์— ๊ฐ€์ด๋“œ๋ฅผ ์ œ๊ณตํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค:
    • Routes vs Ingress: OKD๋Š” Route๋ผ๋Š” ๊ณ ์œ  ๋ฆฌ์†Œ์Šค๋ฅผ ์ œ๊ณตํ•˜๋ฉฐ, ์ด๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ HAProxy ๊ธฐ๋ฐ˜ ์ธ๊ทธ๋ ˆ์Šค ์ปจํŠธ๋กค๋Ÿฌ์™€ ํ•จ๊ป˜ ์ž‘๋™ํ•ด์š”.
    • ํ…œํ”Œ๋ฆฟ๊ณผ ์˜คํผ๋ ˆ์ดํ„ฐ: OKD์—์„œ ์ œ๊ณตํ•˜๋Š” ํ…œํ”Œ๋ฆฟ๊ณผ ์˜คํผ๋ ˆ์ดํ„ฐ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ฐฐํฌ๋ฅผ ๋‹จ์ˆœํ™”ํ•  ์ˆ˜ ์žˆ์–ด์š”.
    • ํ”„๋กœ์ ํŠธ์™€ ๋„ค์ž„์ŠคํŽ˜์ด์Šค: OKD์˜ ํ”„๋กœ์ ํŠธ๋Š” ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค ๋„ค์ž„์ŠคํŽ˜์ด์Šค์˜ ํ™•์žฅ ๊ฐœ๋…์œผ๋กœ, ์ถ”๊ฐ€์ ์ธ ๋ณด์•ˆ ๋ฐ ๊ด€๋ฆฌ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ด์š”.
    • DeploymentConfig: OKD์—์„œ๋Š” Deployment ๋Œ€์‹  DeploymentConfig๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋” ์„ธ๋ฐ€ํ•œ ๋ฐฐํฌ ์ „๋žต์„ ์ œ๊ณตํ•ด์š”.

์ปค์Šคํ…€ ๋ฆฌ์†Œ์Šค ๋ฐ ํ™•์žฅ ๊ตฌ์„ฑ

  • ํŠน์ • ์š”๊ตฌ ์‚ฌํ•ญ์— ๋”ฐ๋ผ ์ถ”๊ฐ€ ๊ตฌ์„ฑ์ด ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:
    • ์ปค์Šคํ…€ ๋ฆฌ์†Œ์Šค(CustomResources) ์ •์˜๋ฅผ ํ†ตํ•ด OKD์—์„œ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ œ๊ณตํ•˜์ง€ ์•Š๋Š” ๋ฆฌ์†Œ์Šค ํƒ€์ž…์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์–ด์š”.
    • ์Šคํ…Œ์ดํŠธํ’€์…‹(StatefulSet) ๊ตฌ์„ฑ์œผ๋กœ ์ƒํƒœ ์œ ์ง€๊ฐ€ ํ•„์š”ํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜(์˜ˆ: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค)์„ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด์š”.
    • ์„œ๋น„์Šค ๋ฉ”์‹œ(Service Mesh): OKD์—์„œ๋Š” Red Hat Service Mesh(Istio ๊ธฐ๋ฐ˜)๋ฅผ ํ†ตํ•ฉํ•˜์—ฌ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ๊ฐ„ ํ†ต์‹ ์„ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด์š”.

์ธ๊ทธ๋ ˆ์Šค ๋ฐ ํŠธ๋ž˜ํ”ฝ ๊ด€๋ฆฌ ์˜ต์…˜

  • OKD ํ™˜๊ฒฝ์—์„œ ๋‹ค์–‘ํ•œ ์ธ๊ทธ๋ ˆ์Šค ์˜ต์…˜์„ ๊ณ ๋ คํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:
    • ๊ธฐ๋ณธ HAProxy ์ธ๊ทธ๋ ˆ์Šค: OKD๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ HAProxy ๊ธฐ๋ฐ˜ ์ธ๊ทธ๋ ˆ์Šค ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ์ œ๊ณตํ•ด์š”.
    • NGINX ์ธ๊ทธ๋ ˆ์Šค ์ปจํŠธ๋กค๋Ÿฌ: ๊ณ ๋ถ€ํ•˜ ํŠธ๋ž˜ํ”ฝ ํ™˜๊ฒฝ์—์„œ๋Š” NGINX ์ธ๊ทธ๋ ˆ์Šค ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ๋Œ€์•ˆ์œผ๋กœ ์„ค์น˜ํ•  ์ˆ˜ ์žˆ์–ด์š”.
      • ์žฅ์ : ๊ณ ์„ฑ๋Šฅ, ์„ธ๋ฐ€ํ•œ ์„ค์ • ๊ฐ€๋Šฅ, ๊ด‘๋ฒ”์œ„ํ•œ ์ปค๋ฎค๋‹ˆํ‹ฐ ์ง€์›
      • ๋‹จ์ : ์ถ”๊ฐ€ ๊ตฌ์„ฑ ๋ฐ ๊ด€๋ฆฌ ํ•„์š”
    • API ๊ฒŒ์ดํŠธ์›จ์ด: Kong, Ambassador ๋“ฑ์˜ API ๊ฒŒ์ดํŠธ์›จ์ด๋ฅผ OKD ์œ„์— ์„ค์น˜ํ•˜์—ฌ ๊ณ ๊ธ‰ ๋ผ์šฐํŒ… ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์–ด์š”.

OKD์˜ ๋Œ€์•ˆ์ฑ… ๊ฒ€ํ† 

  • ํ˜„์žฌ OKD๋ฅผ ์‚ฌ์šฉ ์ค‘์ด์ง€๋งŒ, ํ•„์š”์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค ๋ฐฐํฌํŒ๋„ ๊ณ ๋ คํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:
    • EKS(Amazon Elastic Kubernetes Service): AWS ํ™˜๊ฒฝ์—์„œ ๊ด€๋ฆฌํ˜•์œผ๋กœ ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค๋ฅผ ์šด์˜ํ•˜๊ณ  ์‹ถ์„ ๊ฒฝ์šฐ
      • ์žฅ์ : ๊ด€๋ฆฌ ๋ถ€๋‹ด ๊ฐ์†Œ, AWS ์„œ๋น„์Šค์™€์˜ ํ†ตํ•ฉ ์šฉ์ด
      • ๋‹จ์ : OKD์˜ ์ถ”๊ฐ€ ๊ธฐ๋Šฅ๋“ค(CI/CD ํ†ตํ•ฉ, ๊ฐœ๋ฐœ์ž ์นดํƒˆ๋กœ๊ทธ ๋“ฑ) ๋ถ€์žฌ
    • GKE(Google Kubernetes Engine): Google Cloud์—์„œ ๊ด€๋ฆฌํ˜• ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค๋ฅผ ์›ํ•  ๊ฒฝ์šฐ
    • AKS(Azure Kubernetes Service): Microsoft Azure์—์„œ ๊ด€๋ฆฌํ˜• ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค๋ฅผ ์›ํ•  ๊ฒฝ์šฐ
    • Rancher: ๋‹ค์ค‘ ํด๋Ÿฌ์Šคํ„ฐ ๊ด€๋ฆฌ ๊ธฐ๋Šฅ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ์˜ ๋Œ€์•ˆ

๊ฐœ๋ฐœ์ž์™€ ์šด์˜ ํ˜‘์—…

  • ๊ฐœ๋ฐœ ํŒ€์—์„œ๋„ ์ธํ”„๋ผ๋ฅผ ์ถฉ๋ถ„ํžˆ ์ดํ•ดํ•  ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค:
    • OKD์˜ ์›น ์ฝ˜์†”์„ ํ†ตํ•ด ๊ฐœ๋ฐœ์ž๋“ค๋„ ๋ฐฐํฌ ๋ฐ ๋ชจ๋‹ˆํ„ฐ๋ง ๊ณผ์ •์— ์ฐธ์—ฌํ•  ์ˆ˜ ์žˆ์–ด์š”.
    • ๊ฐœ๋ฐœ์ž์™€ ์šด์˜ํŒ€์ด ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” OKD์˜ CI/CD ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌ์„ฑ์ด ๊ฐ€๋Šฅํ•ด์š”.
    • ๊ฐœ๋ฐœ์ž๋“ค์ด ์ง์ ‘ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ฆฌ์†Œ์Šค ์š”๊ตฌ์‚ฌํ•ญ(CPU, ๋ฉ”๋ชจ๋ฆฌ, ์Šคํ† ๋ฆฌ์ง€ ๋“ฑ)์„ ์ •์˜ํ•˜๋„๋ก ๊ฐ€์ด๋“œํ•ด์ค˜์•ผ ํ•ด์š”.

๋ฆฌ์†Œ์Šค ๊ด€๋ฆฌ ์ „๋žต โš™๏ธ

  • ํŠนํžˆ ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค ํ™˜๊ฒฝ์—์„œ๋Š” ๋ฆฌ์†Œ์Šค๋ฅผ ์–ด๋–ป๊ฒŒ ์ชผ๊ฐœ๊ณ  ๋‚˜๋ˆŒ ๊ฒƒ์ธ์ง€ ๊ฒฐ์ •์ด ํ•„์š”ํ•ด์š”.
  • ์–ด๋А ๋ถ€๋ถ„์„ ConfigMap์— ๋‘๊ณ  ์–ด๋А ๋ถ€๋ถ„์„ Secret์œผ๋กœ ๊ด€๋ฆฌํ•  ๊ฒƒ์ธ์ง€์— ๋Œ€ํ•œ ์„ค๊ณ„๋„ ๊ฐœ๋ฐœ ํŒ€์—์„œ ์ œ๊ณตํ•˜๋ฉด ์ข‹์•„์š”.
    • ConfigMap: ํ™˜๊ฒฝ๋ณ„ ์„ค์ •, ๋กœ๊ทธ ๋ ˆ๋ฒจ, ์•ฑ ๊ตฌ์„ฑ ๋“ฑ ๋ฏผ๊ฐํ•˜์ง€ ์•Š์€ ์„ค์ •์ด์—์š”.
    • Secret: API ํ‚ค, ์ธ์ฆ์„œ, ํŒจ์Šค์›Œ๋“œ ๋“ฑ ๋ฏผ๊ฐํ•œ ์ •๋ณด์˜ˆ์š”.

๋งˆ์น˜๋ฉฐ 

์ด์ƒ์œผ๋กœ ์ธํ”„๋ผ์™€ ๋ฐ๋ธŒ์˜ต์Šค์— ๊ด€ํ•œ ์ค‘์š” ๊ฐœ๋…๋“ค์„ ๊ฐ„๋žตํžˆ ๋ณด์•˜์Šต๋‹ˆ๋‹ค! ์ด ๊ธ€์ด ์—ฌ๋Ÿฌ๋ถ„์˜ ํ•™์Šต๊ณผ ์—…๋ฌด์— ๋„์›€์ด ๋˜์—ˆ์œผ๋ฉด ์ข‹๊ฒ ์–ด์š”.

๋‹ค์‹œ ํ•œ๋ฒˆ ๋ง์”€๋“œ๋ฆฌ์ง€๋งŒ, ์ด ๊ธ€์€ ์ œ ์ฃผ๊ด€์ ์ธ ๊ด€์ ๊ณผ ๊ฒฝํ—˜์„ ๋ฐ”ํƒ•์œผ๋กœ ์ž‘์„ฑ๋˜์—ˆ๋‹ต๋‹ˆ๋‹ค. ๊ผญ ๋‹ค๋ฅธ ์ž๋ฃŒ๋“ค๋„ ํ•จ๊ป˜ ์ฐธ๊ณ ํ•˜์‹œ๊ณ , ์‹ค๋ฌด์— ์ ์šฉํ•˜์‹ค ๋•Œ๋Š” ๊ต์ฐจ๊ฒ€์ฆ์„ ํ•˜์‹œ๋Š” ๊ฒƒ์„ ์ถ”์ฒœ๋“œ๋ ค์š”!

๊ถ๊ธˆํ•œ ์ ์ด๋‚˜ ์˜๊ฒฌ์ด ์žˆ์œผ์‹œ๋ฉด ์–ธ์ œ๋“ ์ง€ ๋Œ“๊ธ€๋กœ ๋‚จ๊ฒจ์ฃผ์„ธ์š”. ํ•จ๊ป˜ ์„ฑ์žฅํ•˜๋Š” ๊ธฐํšŒ๊ฐ€ ๋˜์—ˆ์œผ๋ฉด ์ข‹๊ฒ ์Šต๋‹ˆ๋‹ค. 

๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค!

์‹œ๊ฐ„์ด ์ง€๋‚œ ํ›„, ์ฝ”๋“œ๋ฅผ ๋ฆฌํŒฉํ† ๋ง ํ•˜๋Š” ๊ณผ์ •์—์„œ ์ž‘์—… ์ด์œ ์— ๋Œ€ํ•ด ์ฐพ์•„๋ณด๊ธฐ ์œ„ํ•ด TIL ์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค!

 

์˜ค๋Š˜์€ Vanilla JavaScript๋กœ ์›น ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค๋˜ ์ค‘ ๋งˆ์šฐ์Šค ๋‹ค์šด(mousedown) ์ด๋ฒคํŠธ์™€ ๋งˆ์šฐ์Šค ์—…(mouseup) ์ด๋ฒคํŠธ, ๊ทธ๋ฆฌ๊ณ  ํด๋ฆญ(click) ์ด๋ฒคํŠธ ๊ฐ„์˜ ์ถฉ๋Œ์ด ๋ฐœ์ƒํ•ด, ๊ฐ ์ด๋ฒคํŠธ์˜ ๋™์ž‘์ด ์„œ๋กœ ๋…๋ฆฝ์ ์œผ๋กœ ์‹คํ–‰๋  ์ˆ˜ ์žˆ๋„๋ก ์กฐ์ •ํ•œ ๋‚ด์šฉ์„ ๊ฐ„๋‹จํžˆ ๊ธฐ๋กํ•˜๋ คํ•ฉ๋‹ˆ๋‹ค.

 

 

eventType์— ์™œ click์ด ์˜ค์ง€ ์•Š์„๊นŒ..?!

๋ฌธ์ œ ์ƒํ™ฉ: ๋‘ ๊ฒฝ์šฐ์˜ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋ง

์‚ฌ์šฉ์ž๊ฐ€ ์ƒํ’ˆ ๋ฒ„ํŠผ์„ ํด๋ฆญํ–ˆ์„ ๋•Œ, ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค:

  1. ์ž”์•ก์ด ๋ถ€์กฑํ•œ ๊ฒฝ์šฐ
  2. ์ž”์•ก์ด ์ถฉ๋ถ„ํ•œ ๊ฒฝ์šฐ
  3. ๋งˆ์šฐ์Šค ๋‹ค์šด/์—… ์ด๋ฒคํŠธ์™€ ํด๋ฆญ ์ด๋ฒคํŠธ์˜ ์ƒํ˜ธ์ž‘์šฉ

์ด๋Ÿฌํ•œ ์ƒํ™ฉ์—์„œ ๋งˆ์šฐ์Šค ์—… ์ด๋ฒคํŠธ์˜ ๋™์ž‘์— ๊ฐ€๋ ค์ ธ ํด๋ฆญ ์ด๋ฒคํŠธ๊ฐ€ ๋™์ž‘ํ•˜์ง€ ์•Š๋Š” ์ƒํ™ฉ์ด ๋ฐœ์ƒํ–ˆ์–ด์š”.

 

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•: ์กฐ๊ฑด๋ถ€ ์ด๋ฒคํŠธ ๋“ฑ๋ก

๋จผ์ € ๊ธฐ์กด ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.

  setEvent() {
    const { onSelect, onMouseDown, onMouseUp } = this.props;

    if (onSelect) {
      this.addEvent('click', '.product-button', (e) => {
        const button = e.target.closest('.product-button');
        if (!button) return;
        const productId = Number(button.dataset.id);
        onSelect(productId);
      });
    }

    if (onMouseDown) {
      this.addEvent('mousedown', '.product-button', onMouseDown);
    }

    if (onMouseUp) {
      this.addEvent('mouseup', '.product-button', onMouseUp);
    }
  }

 

๋ณ€๊ฒฝ๋œ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.

setEvent() {
  const { onSelect, onMouseDown, onMouseUp, product } = this.props;

  // ๊ฐ€๊ฒฉ์ด ๋ถ€์กฑํ•  ๋•Œ๋งŒ mousedown/mouseup ์ด๋ฒคํŠธ ๋“ฑ๋ก
  if (onMouseDown && this.state.balance < product.price) {
    this.addEvent('mousedown', '.product-button', () => {
      onMouseDown(product);
    });
    this.addEvent('mouseup', '.product-button', () => {
      onMouseUp();
    });
  } else {
    // ๊ฐ€๊ฒฉ์ด ์ถฉ๋ถ„ํ•˜๊ฑฐ๋‚˜ ์กฐ๊ฑด์— ๋งž์ง€ ์•Š์œผ๋ฉด click ์ด๋ฒคํŠธ๋งŒ ๋“ฑ๋ก
    this.addEvent('click', '.product-button', (e) => {
      const button = e.target.closest('.product-button');
      if (!button) return;
      const productId = Number(button.dataset.id);
      onSelect(productId);
    });
  }
}

 

ํ•ต์‹ฌ ์ ‘๊ทผ ๋ฐฉ์‹

  1. ์กฐ๊ฑด๋ถ€ ์ด๋ฒคํŠธ ๋“ฑ๋ก: ํ˜„์žฌ ์ƒํƒœ์— ๋”ฐ๋ผ ์ ์ ˆํ•œ ์ด๋ฒคํŠธ๋ฅผ ๋™์ ์œผ๋กœ ๋ฐ”์ธ๋”ฉํ•ฉ๋‹ˆ๋‹ค.
  2. ์ด๋ฒคํŠธ ์œ„์ž„: closest() ๋ฉ”์„œ๋“œ๋ฅผ ํ™œ์šฉํ•ด ์ด๋ฒคํŠธ ๋ฒ„๋ธ”๋ง์„ ํšจ์œจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. (๊ธ€์—๋Š” ์—†์ง€๋งŒ component.js์—์„œ ์ฒ˜๋ฆฌ)
  3. ์ƒํƒœ ๊ธฐ๋ฐ˜ ๋กœ์ง: balance์™€ product.price๋ฅผ ๋น„๊ตํ•˜์—ฌ ์ด๋ฒคํŠธ ํ๋ฆ„์„ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค.

 

์˜ค๋Š˜ HTTP ๋ฉ”์„œ๋“œ์— ๋Œ€ํ•œ ์งˆ๋ฌธ์„ ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค. ๋‹ต๋ณ€์„ ๋“œ๋ฆฌ๋ ค๊ณ  ํ•˜๋‹ค๋ณด๋‹ˆ ์ œ๊ฐ€ GET, POST, DELETE, PUT, PATCH ๊ฐ™์€ ๊ธฐ๋ณธ์ ์ธ ๋ฉ”์„œ๋“œ ์™ธ์—๋Š” ์ž˜ ๋ชจ๋ฅธ๋‹ค๋Š”๊ฑธ ์•Œ๊ฒŒ๋์–ด์š”๐Ÿฅฒ ํŠนํžˆ HEAD๋‚˜ OPTIONS ๊ฐ™์€ ๋ฉ”์„œ๋“œ๋“ค์€ ์กด์žฌ์˜ ์œ ๋ฌด๋Š” ์•Œ์•˜์œผ๋‚˜, ์‹ค์ œ๋กœ ์–ด๋–ป๊ฒŒ ํ™œ์šฉ๋˜๋Š”์ง€ ๋ช…ํ™•ํ•˜๊ฒŒ ์ธ์ง€ํ•˜์ง€๋„ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.

 

๊ทธ๋ž˜์„œ ์‹ ์ž… ์‹œ์ ˆ์˜ ๊ธฐ์–ต๋„ ๋˜์‚ด๋ฆฌ๊ณ , ๋ถ€์กฑํ–ˆ๋˜ ๋ถ€๋ถ„๋„ ์ฑ„์›Œ๋ณด๊ณ ์ž HTTP ๋ฉ”์„œ๋“œ์™€ ๊ทธ ์‚ฌ์šฉ๋ฒ•์— ๋Œ€ํ•ด ์ •๋ฆฌํ•ด๋ณด๋ ค๊ณ  ํ•ด์š”.

ํŠนํžˆ React์™€ Next.js๋ฅผ ์‚ฌ์šฉํ•˜์‹œ๋Š” ๋ถ„๋“ค์„ ์œ„ํ•ด ์‹ค์ œ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ ์–ด๋–ป๊ฒŒ ํ™œ์šฉ๋˜๋Š”์ง€ ๊ตฌ์ฒด์ ์ธ ์˜ˆ์‹œ์™€ ํ•จ๊ป˜ ์„ค๋ช…ํ•ด๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค.

 

HTTP ๋ฉ”์„œ๋“œ Next.js ๋Š” Pages Router๋ฐฉ์‹์ด ๋” ์ต์ˆ™ํ•˜์‹  ๋ถ„๋“ค์„ ์œ„ํ•ด API Routes handler ๋ฌธ๋ฒ•(pages/api/ ๋””๋ ‰ํ† ๋ฆฌ ์‚ฌ์šฉ)์ธ Next.js 12 ์ด์ „์˜ ๋ฐฉ์‹์œผ๋กœ ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.

App Router ๋ฐฉ์‹๋งŒ์„ ์•Œ๊ณ  ๊ณ„์‹  ๋ถ„๋“ค์€, export async function GET() ์ฒ˜๋Ÿผ HTTP ๋ฉ”์„œ๋“œ๋ฅผ ํ•จ์ˆ˜ ์ด๋ฆ„์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ์ƒˆ๋กœ์šด convention ๋ฐฉ์‹ ๋“ฑ์„ ํฌํ•จํ•œ ์—ฌ๋Ÿฌ ์ฐจ์ด์ ์ด ์žˆ์ง€๋งŒ ๋ฌธ์„œ๋ฅผ ํ™•์ธํ•˜๋ฉฐ ์ง„ํ–‰ํ•˜์‹ ๋‹ค๋ฉด ๋ฌธ์ œ์—†์ด ์ดํ•ดํ•˜์‹ค ์ˆ˜ ์žˆ์„ ๊ฑฐ๋ผ ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

 

 

 

HTTP Method

HTTP ๋ฉ”์„œ๋“œ๋ž€?

HTTP ๋ฉ”์„œ๋“œ๋Š” ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„ ๊ฐ„์˜ ํ†ต์‹ ์—์„œ ์–ด๋–ค ๋™์ž‘์„ ์ˆ˜ํ–‰ํ• ์ง€ ์ง€์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. REST API์—์„œ ๋งค์šฐ ์ค‘์š”ํ•œ ์—ญํ• ์„ ํ•˜๋ฉฐ, ๊ฐ ๋ฉ”์„œ๋“œ๋Š” ํŠน์ •ํ•œ ๋ชฉ์ ์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ์ œ๋Œ€๋กœ ์ดํ•ดํ•˜๊ณ  ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์›น ๊ฐœ๋ฐœ์˜ ๊ธฐ์ดˆ๊ฐ€ ๋˜์ฃ .

 

์ฃผ์š” HTTP ๋ฉ”์„œ๋“œ ์‚ดํŽด๋ณด๊ธฐ

1. GET ๋ฉ”์„œ๋“œ

GET์€ ๋ฆฌ์†Œ์Šค๋ฅผ ์กฐํšŒํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋Š” ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ๋ฉ”์„œ๋“œ์ž…๋‹ˆ๋‹ค.

 

React์—์„œ์˜ ์‚ฌ์šฉ ์˜ˆ์‹œ:

// React ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ฐ์ดํ„ฐ ์กฐํšŒ
const UserProfile = () => {
  const [user, setUser] = useState(null);

  useEffect(() => {
    const fetchUser = async () => {
      try {
        const response = await fetch('/api/users/1');
        const data = await response.json();
        setUser(data);
      } catch (error) {
        console.error('Error fetching user:', error);
      }
    };

    fetchUser();
  }, []);

  return (
    <div>
      {user && <h1>{user.name}</h1>}
    </div>
  );
};

 

Next.js API Route์—์„œ์˜ ์ฒ˜๋ฆฌ:

// pages/api/users/[id].ts
import type { NextApiRequest, NextApiResponse } from 'next'

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method !== 'GET') {
    return res.status(405).json({ message: 'Method not allowed' });
  }

  const { id } = req.query;
  // DB ์กฐํšŒ ๋กœ์ง
  const user = await prisma.user.findUnique({
    where: { id: Number(id) }
  });

  res.status(200).json(user);
}

 

2. POST ๋ฉ”์„œ๋“œ

POST๋Š” ์ƒˆ๋กœ์šด ๋ฆฌ์†Œ์Šค๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

 

React์—์„œ์˜ ์‚ฌ์šฉ ์˜ˆ์‹œ:

// ์ƒˆ ์‚ฌ์šฉ์ž ๋“ฑ๋ก ํผ
const RegisterForm = () => {
  const handleSubmit = async (event) => {
    event.preventDefault();
    const formData = new FormData(event.target);

    try {
      const response = await fetch('/api/users', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          name: formData.get('name'),
          email: formData.get('email'),
        }),
      });

      if (!response.ok) throw new Error('Registration failed');
      
      const data = await response.json();
      // ์„ฑ๊ณต ์ฒ˜๋ฆฌ
    } catch (error) {
      // ์—๋Ÿฌ ์ฒ˜๋ฆฌ
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="name" type="text" required />
      <input name="email" type="email" required />
      <button type="submit">๋“ฑ๋ก</button>
    </form>
  );
};

 

3. PUT๊ณผ PATCH ๋ฉ”์„œ๋“œ

// pages/api/users/[id].ts
export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const { id } = req.query;

  if (req.method === 'PUT') {
    // ์ „์ฒด ๋ฐ์ดํ„ฐ ๊ต์ฒด
    const userData = req.body;
    await prisma.user.update({
      where: { id: Number(id) },
      data: userData,
    });
    return res.status(200).json({ message: 'User updated' });
  }

  if (req.method === 'PATCH') {
    // ๋ถ€๋ถ„ ๋ฐ์ดํ„ฐ ์ˆ˜์ •
    const updates = req.body;
    await prisma.user.update({
      where: { id: Number(id) },
      data: updates,
    });
    return res.status(200).json({ message: 'User partially updated' });
  }
}

 

4. DELETE ๋ฉ”์„œ๋“œ

React์—์„œ์˜ ์‚ฌ์šฉ ์˜ˆ์‹œ:

const DeleteUserButton = ({ userId }) => {
  const handleDelete = async () => {
    try {
      const response = await fetch(`/api/users/${userId}`, {
        method: 'DELETE',
      });

      if (!response.ok) throw new Error('Delete failed');
      // ์„ฑ๊ณต ์ฒ˜๋ฆฌ
    } catch (error) {
      // ์—๋Ÿฌ ์ฒ˜๋ฆฌ
    }
  };

  return (
    <button onClick={handleDelete}>
      ์‚ฌ์šฉ์ž ์‚ญ์ œ
    </button>
  );
};

 

React์™€ Next.js์—์„œ๋Š” ์ด๋Ÿฌํ•œ ๋ฉ”์„œ๋“œ๋“ค์„ ์‚ฌ์šฉํ•˜์—ฌ ์„œ๋ฒ„์™€ ํšจ๊ณผ์ ์œผ๋กœ ํ†ต์‹ ํ•˜๊ณ , RESTful API๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

์ž์ฃผ ์‚ฌ์šฉ๋˜์ง€ ์•Š์ง€๋งŒ ์•Œ์•„๋‘๋ฉด ์œ ์šฉํ•œ HTTP ๋ฉ”์„œ๋“œ๋“ค

1. HEAD ๋ฉ”์„œ๋“œ

HEAD ๋ฉ”์„œ๋“œ๋Š” GET๊ณผ ๋™์ผํ•œ ์š”์ฒญ์„ ๋ณด๋‚ด์ง€๋งŒ, ์‘๋‹ต์œผ๋กœ ํ—ค๋”๋งŒ ๋ฐ›๊ณ  ๋ณธ๋ฌธ์€ ๋ฐ›์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

 

์ฃผ์š” ์‚ฌ์šฉ ์‚ฌ๋ก€:

  • ํฐ ํŒŒ์ผ์„ ๋‹ค์šด๋กœ๋“œํ•˜๊ธฐ ์ „์— ํŒŒ์ผ ํฌ๊ธฐ ํ™•์ธ
  • ๋ฆฌ์†Œ์Šค๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ๋Š”์ง€ ๊ฒ€์‚ฌ(Last-Modified ํ™•์ธ)
  • ๋ฆฌ์†Œ์Šค ์กด์žฌ ์—ฌ๋ถ€ ํ™•์ธ
// Next.js์—์„œ HEAD ๋ฉ”์„œ๋“œ ์ฒ˜๋ฆฌ ์˜ˆ์‹œ
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method === 'HEAD') {
    // ํŒŒ์ผ ์ •๋ณด๋งŒ ํ™•์ธ
    const fileStats = await getFileStats('large-file.pdf');
    
    res.setHeader('Content-Length', fileStats.size);
    res.setHeader('Last-Modified', fileStats.mtime.toUTCString());
    return res.status(200).end();
  }
}

// React์—์„œ HEAD ์š”์ฒญ ์˜ˆ์‹œ
const checkFileDetails = async () => {
  try {
    const response = await fetch('/api/files/large-file.pdf', {
      method: 'HEAD'
    });
    
    const fileSize = response.headers.get('Content-Length');
    const lastModified = response.headers.get('Last-Modified');
    
    console.log(`File size: ${fileSize} bytes`);
    console.log(`Last modified: ${lastModified}`);
  } catch (error) {
    console.error('Error checking file:', error);
  }
};

 

1. OPTIONS ๋ฉ”์„œ๋“œ์™€ CORS

OPTIONS๋Š” ์„œ๋ฒ„๊ฐ€ ์ง€์›ํ•˜๋Š” ๋ฉ”์„œ๋“œ์™€ ๊ธฐ๋Šฅ์„ ํ™•์ธํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. 

์ฃผ๋กœ CORS์˜ Preflight ์š”์ฒญ์—์„œ ํ•ต์‹ฌ์ ์ธ ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.

 

์ฃผ์š” ์‚ฌ์šฉ ์‚ฌ๋ก€:

  • CORS ์‚ฌ์ „ ๊ฒ€์‚ฌ
  • ์„œ๋ฒ„ ์ง€์› ๊ธฐ๋Šฅ ํ™•์ธ
  • API ์—”๋“œํฌ์ธํŠธ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์กฐํšŒ

 

Next.js์—์„œ CORS ์„ค์ • ์˜ˆ์‹œ:

// pages/api/users/[id].ts
import Cors from 'cors';

// CORS ๋ฏธ๋“ค์›จ์–ด ์ดˆ๊ธฐํ™”
const cors = Cors({
  methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
  origin: ['https://allowed-origin.com'],
  credentials: true,
});

// ๋ฏธ๋“ค์›จ์–ด ์‹คํ–‰ ํ•จ์ˆ˜
const runMiddleware = (req: NextApiRequest, res: NextApiResponse, fn: Function) => {
  return new Promise((resolve, reject) => {
    fn(req, res, (result: any) => {
      if (result instanceof Error) {
        return reject(result);
      }
      return resolve(result);
    });
  });
};

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  // CORS ๋ฏธ๋“ค์›จ์–ด ์‹คํ–‰
  await runMiddleware(req, res, cors);

  // OPTIONS ์š”์ฒญ ์ฒ˜๋ฆฌ
  if (req.method === 'OPTIONS') {
    return res.status(200).end();
  }

  // ๋‹ค๋ฅธ ๋ฉ”์„œ๋“œ ์ฒ˜๋ฆฌ...
}

 

๋ธŒ๋ผ์šฐ์ €์˜ Preflight ์š”์ฒญ ์˜ˆ์‹œ:

// React์—์„œ CORS ๊ด€๋ จ OPTIONS ์š”์ฒญ์ด ๋ฐœ์ƒํ•˜๋Š” ์ƒํ™ฉ
const fetchWithCustomHeaders = async () => {
  try {
    const response = await fetch('https://api.example.com/data', {
      method: 'GET',
      headers: {
        'Custom-Header': 'value',  // ์ปค์Šคํ…€ ํ—ค๋” ์ถ”๊ฐ€์‹œ ์ž๋™์œผ๋กœ preflight ์š”์ฒญ ๋ฐœ์ƒ
      },
      credentials: 'include',  // ์ธ์ฆ ์ •๋ณด ํฌํ•จ
    });
    
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Error:', error);
  }
};

 

CORS์™€ Preflight ์š”์ฒญ์˜ ์ดํ•ด

๋ธŒ๋ผ์šฐ์ €๋Š” ํŠน์ • ์กฐ๊ฑด์—์„œ ์‹ค์ œ ์š”์ฒญ ์ „์— OPTIONS ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•œ Preflight ์š”์ฒญ์„ ์ž๋™์œผ๋กœ ๋ณด๋ƒ…๋‹ˆ๋‹ค.

 

Preflight ์š”์ฒญ์ด ๋ฐœ์ƒํ•˜๋Š” ๊ฒฝ์šฐ:

  1. ์ปค์Šคํ…€ ํ—ค๋”๋ฅผ ํฌํ•จํ•  ๋•Œ (์˜ˆ: Authorization)
  2. ๋‹จ์ˆœํ•˜์ง€ ์•Š์€ Content-Type์„ ์‚ฌ์šฉํ•  ๋•Œ
  3. ๋‹จ์ˆœํ•˜์ง€ ์•Š์€ ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ (GET, HEAD, POST ์™ธ)
// Preflight ์š”์ฒญ ์˜ˆ์‹œ (๋ธŒ๋ผ์šฐ์ € ๊ฐœ๋ฐœ์ž ๋„๊ตฌ์—์„œ ๋ณด์ด๋Š” ํ˜•ํƒœ)
OPTIONS /api/data HTTP/1.1
Host: api.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization, content-type
Origin: https://your-app.com

// ์„œ๋ฒ„ ์‘๋‹ต ์˜ˆ์‹œ
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://your-app.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: authorization, content-type
Access-Control-Max-Age: 86400

 

3. CONNECT ๋ฉ”์„œ๋“œ

CONNECT๋Š” ํ”„๋ก์‹œ ์„œ๋ฒ„๋ฅผ ํ†ตํ•ด SSL/TLS ํ„ฐ๋„์„ ๊ตฌ์„ฑํ•  ๋•Œ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

์ฃผ์š” ์‚ฌ์šฉ ์‚ฌ๋ก€:

  • HTTPS ํ”„๋ก์‹œ ์—ฐ๊ฒฐ ์„ค์ •
  • ์›น์†Œ์ผ“ ์—ฐ๊ฒฐ ์—…๊ทธ๋ ˆ์ด๋“œ
  • ํ”„๋ก์‹œ ์„œ๋ฒ„๋ฅผ ํ†ตํ•œ ์•”ํ˜ธํ™”๋œ ํ†ต์‹ 
// Node.js์—์„œ CONNECT ์š”์ฒญ ์ฒ˜๋ฆฌ ์˜ˆ์‹œ
const net = require('net');
const server = require('http').createServer();

server.on('connect', (req, clientSocket, head) => {
  // ํ”„๋ก์‹œ ์—ฐ๊ฒฐ ์„ค์ •
  const { port, hostname } = new URL(`http://${req.url}`);
  const serverSocket = net.connect(port || 80, hostname, () => {
    clientSocket.write('HTTP/1.1 200 Connection Established\r\n\r\n');
    serverSocket.write(head);
    serverSocket.pipe(clientSocket);
    clientSocket.pipe(serverSocket);
  });
});

 

4. TRACE ๋ฉ”์„œ๋“œ

TRACE๋Š” ์š”์ฒญ ๋ฉ”์‹œ์ง€๊ฐ€ ํ”„๋ก์‹œ๋‚˜ ๋ฐฉํ™”๋ฒฝ์„ ํ†ต๊ณผํ•˜๋ฉด์„œ ์–ด๋–ป๊ฒŒ ๋ณ€๊ฒฝ๋˜๋Š”์ง€ ์ถ”์ ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

์ฃผ์š” ์‚ฌ์šฉ ์‚ฌ๋ก€:

  • ๋””๋ฒ„๊น…
  • ์š”์ฒญ ๋ฉ”์‹œ์ง€ ๋ณ€์กฐ ๊ฐ์ง€
  • ํ”„๋ก์‹œ ์ฒด์ธ ์ถ”์ 
// Next.js์—์„œ TRACE ๋ฉ”์„œ๋“œ ์ฒ˜๋ฆฌ ์˜ˆ์‹œ
export default function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method === 'TRACE') {
    // ์š”์ฒญ ํ—ค๋”์™€ ๊ฒฝ๋กœ ์ •๋ณด๋ฅผ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜
    const responseBody = {
      method: req.method,
      url: req.url,
      headers: req.headers,
      path: req.url
    };
    
    res.setHeader('Content-Type', 'message/http');
    return res.status(200).json(responseBody);
  }
}

// ๋ณด์•ˆ์ƒ์˜ ์ด์œ ๋กœ ๋Œ€๋ถ€๋ถ„์˜ ์„œ๋ฒ„๋Š” TRACE ๋ฉ”์„œ๋“œ๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค

 

๋ณด์•ˆ ๊ณ ๋ ค์‚ฌํ•ญ

// Next.js์—์„œ ๋ฉ”์„œ๋“œ ์ œํ•œ ๊ตฌํ˜„ ์˜ˆ์‹œ
export default function handler(req: NextApiRequest, res: NextApiResponse) {
  // ํ—ˆ์šฉ๋˜์ง€ ์•Š๋Š” ๋ฉ”์„œ๋“œ ์ฐจ๋‹จ
  const allowedMethods = ['GET', 'POST', 'OPTIONS'];
  if (!allowedMethods.includes(req.method)) {
    return res.status(405).json({ 
      error: 'Method Not Allowed',
      allowedMethods 
    });
  }

  // TRACE ๋ฉ”์„œ๋“œ๋Š” ๋ช…์‹œ์ ์œผ๋กœ ์ฐจ๋‹จ
  if (req.method === 'TRACE') {
    return res.status(403).json({ 
      error: 'TRACE method is disabled for security reasons' 
    });
  }
}
 

์ด๋Ÿฌํ•œ HTTP ๋ฉ”์„œ๋“œ๋“ค์€ ํŠน์ˆ˜ํ•œ ์ƒํ™ฉ์—์„œ ์œ ์šฉํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ๋ณด์•ˆ์„ ๊ณ ๋ คํ•ด ์‹ ์ค‘ํ•˜๊ฒŒ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

ํŠนํžˆ TRACE ๋ฉ”์„œ๋“œ๋Š” ๋ณด์•ˆ์ƒ์˜ ์ด์œ ๋กœ ๋Œ€๋ถ€๋ถ„์˜ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ ๋น„ํ™œ์„ฑํ™”๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

 

๋ฉฑ๋“ฑ์„ฑ

HTTP ๋ฉ”์„œ๋“œ์— ๋Œ€ํ•œ ๋งˆ์ง€๋ง‰ ์„ค๋ช…์œผ๋กœ ์ค‘์š”ํ•œ ๊ฐœ๋…์ธ ๋ฉฑ๋“ฑ์„ฑ์— ๋Œ€ํ•ด ๊ฐ„๋‹จํžˆ ์„ค๋ช…๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค.

๋ฉฑ๋“ฑ์„ฑ(Idempotency)์ด๋ž€ ๋™์ผํ•œ ์š”์ฒญ์„ ํ•œ ๋ฒˆ ๋ณด๋‚ด๋Š” ๊ฒƒ๊ณผ ์—ฌ๋Ÿฌ ๋ฒˆ ๋ณด๋‚ด๋Š” ๊ฒƒ์ด ์„œ๋ฒ„์˜ ์ƒํƒœ์— ๋™์ผํ•œ ์˜ํ–ฅ์„ ๋ฏธ์น˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

 

์ฃผ์š” HTTP ๋ฉ”์„œ๋“œ์˜ ๋ฉฑ๋“ฑ์„ฑ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

๋ฉฑ๋“ฑ์„ฑ์„ ๊ฐ€์ง„ ๋ฉ”์„œ๋“œ:

  1. GET: ๋ฆฌ์†Œ์Šค๋ฅผ ์กฐํšŒ๋งŒ ํ•˜๋ฏ€๋กœ ๋ฉฑ๋“ฑ
  2. HEAD: GET๊ณผ ๋™์ผํ•˜๊ฒŒ ๋ฆฌ์†Œ์Šค ์ •๋ณด๋งŒ ์กฐํšŒํ•˜๋ฏ€๋กœ ๋ฉฑ๋“ฑ
  3. PUT: ๋ฆฌ์†Œ์Šค๋ฅผ ๋Œ€์ฒด(๋ฎ์–ด์“ฐ๊ธฐ)ํ•˜๋ฏ€๋กœ ๋ฉฑ๋“ฑ
  4. DELETE: ๋ฆฌ์†Œ์Šค๋ฅผ ์‚ญ์ œํ•˜๋ฏ€๋กœ ๋ฉฑ๋“ฑ (์ด๋ฏธ ์‚ญ์ œ๋œ ๋ฆฌ์†Œ์Šค๋ฅผ ๋‹ค์‹œ ์‚ญ์ œํ•ด๋„ ์ƒํƒœ๋Š” ๋™์ผ)
  5. OPTIONS: ์„œ๋ฒ„ ๋ฆฌ์†Œ์Šค๊ฐ€ ์ง€์›ํ•˜๋Š” ๋ฉ”์„œ๋“œ ์ •๋ณด๋ฅผ ์กฐํšŒํ•˜๋ฏ€๋กœ ๋ฉฑ๋“ฑ
  6. TRACE: ์š”์ฒญ ๋ฉ”์‹œ์ง€๋ฅผ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ ๋ฉฑ๋“ฑ

๋ฉฑ๋“ฑ์„ฑ์ด ์—†๋Š” ๋ฉ”์„œ๋“œ:

  1. POST: ์ƒˆ๋กœ์šด ๋ฆฌ์†Œ์Šค ์ƒ์„ฑ ๋˜๋Š” ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ๋ฅผ ์š”์ฒญํ•˜๋ฏ€๋กœ ๋ฉฑ๋“ฑํ•˜์ง€ ์•Š์Œ
    • ์˜ˆ: ๊ฐ™์€ ์ฃผ๋ฌธ์„ ์—ฌ๋Ÿฌ ๋ฒˆ POSTํ•˜๋ฉด ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์ฃผ๋ฌธ์ด ์ƒ์„ฑ๋จ
  2. PATCH: ๋ฆฌ์†Œ์Šค์˜ ๋ถ€๋ถ„ ์ˆ˜์ •์ด๋ฏ€๋กœ ๋ฉฑ๋“ฑํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Œ
    • ์˜ˆ: ์ˆซ์ž๋ฅผ 1์”ฉ ์ฆ๊ฐ€์‹œํ‚ค๋Š” PATCH ์š”์ฒญ์€ ๋ฉฑ๋“ฑํ•˜์ง€ ์•Š์Œ

๋ฉฑ๋“ฑ์„ฑ์ด ์ค‘์š”ํ•œ ์ด์œ :

  1. ๋„คํŠธ์›Œํฌ ์˜ค๋ฅ˜ ์‹œ ์žฌ์‹œ๋„ ์ „๋žต ์ˆ˜๋ฆฝ ๊ฐ€๋Šฅ
  2. ์„œ๋ฒ„์˜ ์•ˆ์ •์„ฑ๊ณผ ์˜ˆ์ธก ๊ฐ€๋Šฅ์„ฑ ๋ณด์žฅ
  3. ์บ์‹ฑ ์ „๋žต ์ˆ˜๋ฆฝ์— ๋„์›€

API๋ฅผ ์„ค๊ณ„ํ•  ๋•Œ๋Š” ๊ฐ ๋ฉ”์„œ๋“œ์˜ ๋ฉฑ๋“ฑ์„ฑ ํŠน์„ฑ์„ ๊ณ ๋ คํ•˜์—ฌ ์ ์ ˆํ•œ ๋ฉ”์„œ๋“œ๋ฅผ ์„ ํƒํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

 

 

GraphQL

๋ฒˆ์™ธ: REST API vs GraphQL 

HTTP ๋ฉ”์„œ๋“œ๋ฅผ ๊ณต๋ถ€ํ•˜๋ฉด์„œ ๋นผ๋†“์„ ์ˆ˜ ์—†๋Š” ์ด์•ผ๊ธฐ๊ฐ€ ํ•˜๋‚˜ ์žˆ๋Š”๋ฐ์š”, ๋ฐ”๋กœ GraphQL์ž…๋‹ˆ๋‹ค. REST API๋งŒ ์‚ฌ์šฉํ•˜๋‹ค๊ฐ€ ์ฒ˜์Œ GraphQL์„ ์ ‘ํ–ˆ์„ ๋•Œ ์ƒ๋‹นํžˆ ์‹ ๊ธฐํ–ˆ์Šต๋‹ˆ๋‹ค. "์•„๋‹ˆ, HTTP ๋ฉ”์„œ๋“œ๋„ ์—†์ด ์–ด๋–ป๊ฒŒ API๋ฅผ ๋งŒ๋“ ๋‹ค๋Š” ๊ฑฐ์ง€?" ํ•˜๋ฉด์„œ์š”. ๐Ÿ˜…

 

GraphQL์ด๋ž€?

GraphQL์€ Facebook์—์„œ ๊ฐœ๋ฐœํ•œ API๋ฅผ ์œ„ํ•œ ์ฟผ๋ฆฌ ์–ธ์–ด์ž…๋‹ˆ๋‹ค. REST API์™€๋Š” ๋‹ฌ๋ฆฌ, GraphQL์€ ๋‹จ์ผ ์—”๋“œํฌ์ธํŠธ์—์„œ ๋ชจ๋“  ๋ฐ์ดํ„ฐ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๊ณ , ํด๋ผ์ด์–ธํŠธ๊ฐ€ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ •ํ™•ํžˆ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

REST API vs GraphQL ๋น„๊ต

// REST API ๋ฐฉ์‹
// GET /api/users/1
// GET /api/users/1/posts
// GET /api/users/1/followers

// GraphQL ๋ฐฉ์‹
const query = `
  query {
    user(id: 1) {
      name
      email
      posts {
        title
        content
      }
      followers {
        name
      }
    }
  }
`;

 

Next.js์—์„œ GraphQL ์‚ฌ์šฉ ์˜ˆ์‹œ

// pages/api/graphql.ts
import { ApolloServer } from 'apollo-server-micro';
import { typeDefs } from './schema';
import { resolvers } from './resolvers';

const apolloServer = new ApolloServer({
  typeDefs,
  resolvers,
});

export const config = {
  api: {
    bodyParser: false,
  },
};

export default async function handler(req, res) {
  await apolloServer.start();
  await apolloServer.createHandler({
    path: '/api/graphql',
  })(req, res);
}

 

React์—์„œ GraphQL ์‚ฌ์šฉ ์˜ˆ์‹œ (Apollo Client)

import { useQuery } from '@apollo/client';

const GET_USER = gql`
  query GetUser($id: ID!) {
    user(id: $id) {
      name
      email
      posts {
        title
      }
    }
  }
`;

function UserProfile({ userId }) {
  const { loading, error, data } = useQuery(GET_USER, {
    variables: { id: userId },
  });

  if (loading) return 'Loading...';
  if (error) return `Error! ${error.message}`;

  return (
    <div>
      <h1>{data.user.name}</h1>
      <p>{data.user.email}</p>
      <h2>Posts</h2>
      <ul>
        {data.user.posts.map(post => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
}

 

REST API vs GraphQL: ๊ฐ๊ฐ์˜ ์žฅ๋‹จ์ 

REST API

๐Ÿ‘ ์žฅ์ :

  • HTTP ์บ์‹ฑ ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ํ™œ์šฉ
  • ์ง๊ด€์ ์ธ ๋ฆฌ์†Œ์Šค ๊ตฌ์กฐ
  • ๋” ๋„๋ฆฌ ์‚ฌ์šฉ๋˜์–ด ํ•™์Šต ์ž๋ฃŒ๊ฐ€ ํ’๋ถ€
  • ๋‹จ์ˆœํ•œ API์—์„œ๋Š” ๊ตฌํ˜„์ด ๋” ๊ฐ„๋‹จ

๐Ÿ‘Ž ๋‹จ์ :

  • Over-fetching/Under-fetching ๋ฌธ์ œ
  • ์—ฌ๋Ÿฌ ๋ฆฌ์†Œ์Šค๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋•Œ ์—ฌ๋Ÿฌ ๋ฒˆ์˜ ์š”์ฒญ ํ•„์š”
  • ์—”๋“œํฌ์ธํŠธ ๊ด€๋ฆฌ๊ฐ€ ๋ณต์žกํ•ด์งˆ ์ˆ˜ ์žˆ์Œ

 

GraphQL

๐Ÿ‘ ์žฅ์ :

  • ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋งŒ ์ •ํ™•ํžˆ ์š”์ฒญ ๊ฐ€๋Šฅ
  • ๋‹จ์ผ ์š”์ฒญ์œผ๋กœ ์—ฌ๋Ÿฌ ๋ฆฌ์†Œ์Šค ์กฐํšŒ ๊ฐ€๋Šฅ
  • ๊ฐ•๋ ฅํ•œ ํƒ€์ž… ์‹œ์Šคํ…œ
  • ์‹ค์‹œ๊ฐ„ ์—…๋ฐ์ดํŠธ ์ง€์› (Subscriptions)

๐Ÿ‘Ž ๋‹จ์ :

  • ํ•™์Šต ๊ณก์„ ์ด ์ƒ๋Œ€์ ์œผ๋กœ ๊ฐ€ํŒŒ๋ฆ„
  • ์บ์‹ฑ ๊ตฌํ˜„์ด ๋” ๋ณต์žก
  • ๋‹จ์ˆœํ•œ API์—์„œ๋Š” ์˜ค๋ฒ„ ์—”์ง€๋‹ˆ์–ด๋ง์ด ๋  ์ˆ˜ ์žˆ์Œ
  • ํŒŒ์ผ ์—…๋กœ๋“œ ์ฒ˜๋ฆฌ๊ฐ€ ๋ณต์žก

 

์‹ค์ œ ์‚ฌ์šฉ ํŒ

// GraphQL๊ณผ REST API๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋Š” ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์ ‘๊ทผ
// pages/api/hybrid/[...path].ts
export default async function handler(req, res) {
  // ํŒŒ์ผ ์—…๋กœ๋“œ๋Š” REST API๋กœ ์ฒ˜๋ฆฌ
  if (req.url.startsWith('/api/upload')) {
    return handleFileUpload(req, res);
  }

  // ๋ณต์žกํ•œ ๋ฐ์ดํ„ฐ ์ฟผ๋ฆฌ๋Š” GraphQL๋กœ ์ฒ˜๋ฆฌ
  if (req.url.startsWith('/api/graphql')) {
    return handleGraphQLRequest(req, res);
  }

  // ๋‹จ์ˆœํ•œ CRUD๋Š” REST API๋กœ ์ฒ˜๋ฆฌ
  return handleRESTRequest(req, res);
}

 

์–ธ์ œ ๋ฌด์—‡์„ ์„ ํƒํ• ๊นŒ?

 

  • REST API๊ฐ€ ์ข‹์€ ๊ฒฝ์šฐ:
    • ๋‹จ์ˆœํ•œ CRUD ์ž‘์—…์ด ์ฃผ๋ฅผ ์ด๋ฃจ๋Š” ๊ฒฝ์šฐ
    • ์บ์‹ฑ์ด ์ค‘์š”ํ•œ ๊ฒฝ์šฐ
    • ํŒŒ์ผ ์—…๋กœ๋“œ/๋‹ค์šด๋กœ๋“œ๊ฐ€ ๋งŽ์€ ๊ฒฝ์šฐ
    • ํŒ€์ด REST์— ๋” ์ต์ˆ™ํ•œ ๊ฒฝ์šฐ
  • GraphQL์ด ์ข‹์€ ๊ฒฝ์šฐ:
    • ๋ณต์žกํ•œ ๋ฐ์ดํ„ฐ ๊ด€๊ณ„๋ฅผ ๋‹ค๋ฃจ๋Š” ๊ฒฝ์šฐ
    • ๋ชจ๋ฐ”์ผ ์•ฑ์—์„œ ๋ฐ์ดํ„ฐ ์‚ฌ์šฉ๋Ÿ‰ ์ตœ์ ํ™”๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ
    • ์‹ค์‹œ๊ฐ„ ์—…๋ฐ์ดํŠธ๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ
    • API ๋ณ€๊ฒฝ์ด ์ž์ฃผ ๋ฐœ์ƒํ•˜๋Š” ๊ฒฝ์šฐ

 

์‹ค์ œ๋กœ ๊ธฐ์—…๋“ค์—์„œ ๋‘ ๋ฐฉ์‹์„ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋Š” ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์ ‘๊ทผ์„ ํƒํ•˜๊ณ  ์žˆ๋Š”๊ฑธ๋กœ ์•Œ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. GitHub์ด ๋Œ€ํ‘œ์ ์ธ ์˜ˆ์‹œ์ธ๋ฐ์š”, ์š”๊ตฌ ์‚ฌํ•ญ์— ๊ฐ€์žฅ ์ž˜ ๋ถ€ํ•ฉํ•˜๊ณ  ๊ฐ€์žฅ ํŽธ์•ˆํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” API๋ฅผ ์‚ฌ์šฉํ•˜๋ผ ๊ถŒ๊ณ ํ•˜๊ณ  ์žˆ๊ณ , ํŒŒ์ผ ์—…๋กœ๋“œ ๊ฐ™์€ ๊ธฐ๋Šฅ์€ REST API๋กœ, ๋ณต์žกํ•œ ๋ฐ์ดํ„ฐ ์ฟผ๋ฆฌ๋Š” GraphQL๋กœ ์ฒ˜๋ฆฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

 

์˜ค๋Š˜์€ ์ œ๊ฐ€ Context API์—์„œ Zustand๋กœ ์ƒํƒœ๊ด€๋ฆฌ ๋„๊ตฌ๋ฅผ ์ „ํ™˜ํ•˜๊ฒŒ ๋œ ๊ณผ์ •๊ณผ 2024๋…„ ํ˜„์žฌ ๋ฆฌ์•กํŠธ ์ƒํƒœ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์˜ ํ˜„ํ™ฉ์— ๋Œ€ํ•ด ์ด์•ผ๊ธฐํ•ด๋ณด๋ ค ํ•ฉ๋‹ˆ๋‹ค.

 

1. Context API์˜ ํ•œ๊ณ„์™€ ์ƒํƒœ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ํ•„์š”์„ฑ

Context API๋กœ ์‹œ์ž‘ํ•œ ์ด์œ 

์ฒ˜์Œ ์•Œ๋ฆฌ๋‹ˆ ํ”„๋กœ์ ํŠธ๋ฅผ ์‹œ์ž‘ํ•  ๋•Œ๋Š” Context API๋งŒ์œผ๋กœ ์ถฉ๋ถ„ํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž ์ธ์ฆ, ํ…Œ๋งˆ, ์–ธ์–ด ์„ค์ • ๋“ฑ ์ „์—ญ์ ์œผ๋กœ ๊ณต์œ ๋˜์–ด์•ผ ํ•˜๋Š” ์ƒํƒœ๋“ค์„ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐ Context API ๋งŒ์œผ๋กœ ์ถฉ๋ถ„ํ–ˆ์Šต๋‹ˆ๋‹ค.

// ์ดˆ๊ธฐ Context API ์‚ฌ์šฉ ์˜ˆ์‹œ
const PetContext = React.createContext<PetContextType | undefined>(undefined);

export const PetProvider: React.FC<PropsWithChildren> = ({ children }) => {
  const [petData, setPetData] = useState<PetData>({
    allergies: [],
    symptoms: [],
    foodLog: []
  });

  return (
    <PetContext.Provider value={{ petData, setPetData }}>
      {children}
    </PetContext.Provider>
  );
};

 

ํ•œ๊ณ„์  ๋ฐœ๊ฒฌ

ํ•˜์ง€๋งŒ ํ”„๋กœ์ ํŠธ๊ฐ€ ์ปค์ง€๋ฉด์„œ ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ํ•œ๊ณ„์ ์ด ๋“œ๋Ÿฌ๋‚˜๊ธฐ ์‹œ์ž‘ํ–ˆ์Šต๋‹ˆ๋‹ค:

 

  • ๋ถˆํ•„์š”ํ•œ ๋ฆฌ๋ Œ๋”๋ง: Context์˜ ๊ฐ’์ด ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ํ•ด๋‹น Context๋ฅผ ๊ตฌ๋…ํ•˜๋Š” ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง๋˜์—ˆ์Šต๋‹ˆ๋‹ค. 
  • ์ฝ”๋“œ ๋ณต์žก๋„ ์ฆ๊ฐ€: ์—ฌ๋Ÿฌ ๊ฐœ์˜ Context๋ฅผ ์ค‘์ฒฉํ•ด์„œ ์‚ฌ์šฉํ•˜๋‹ค ๋ณด๋‹ˆ Provider Hell์ด ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ์ƒํƒœ ์—…๋ฐ์ดํŠธ ๋กœ์ง ๊ด€๋ฆฌ์˜ ์–ด๋ ค์›€: ๋น„๋™๊ธฐ ์ž‘์—…๊ณผ ๋ณต์žกํ•œ ์ƒํƒœ ์—…๋ฐ์ดํŠธ ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐ ํ•œ๊ณ„๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

 

2. ์ฃผ์š” ์ƒํƒœ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋น„๊ต ๋ถ„์„

Redux

Redux๋Š” ์˜ค๋žซ๋™์•ˆ ๋ฆฌ์•กํŠธ ์ƒํƒœ๊ณ„์˜ ํ‘œ์ค€๊ณผ ๊ฐ™์€ ์ƒํƒœ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ž…๋‹ˆ๋‹ค.

Redux์˜ ํ•ต์‹ฌ ๊ฐœ๋…

 

  • Action: ์ƒํƒœ ๋ณ€๊ฒฝ์„ ์œ„ํ•œ ์ด๋ฒคํŠธ๋ฅผ ์„ค๋ช…ํ•˜๋Š” ๊ฐ์ฒด
  • Reducer: Action์„ ๋ฐ›์•„ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ์ˆœ์ˆ˜ ํ•จ์ˆ˜
  • Store: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ „์ฒด ์ƒํƒœ๋ฅผ ๋ณด๊ด€ํ•˜๋Š” ๊ฐ์ฒด

Redux ๋ฏธ๋“ค์›จ์–ด ์ƒํƒœ๊ณ„

 

  • Redux Thunk: ๋น„๋™๊ธฐ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ๋ฏธ๋“ค์›จ์–ด
  • Redux Saga: ๋ณต์žกํ•œ ๋น„๋™๊ธฐ ํ๋ฆ„์„ ์ œ์–ดํ•˜๊ธฐ ์œ„ํ•œ ๋ฏธ๋“ค์›จ์–ด
    • Generator ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•œ ์„ ์–ธ์  ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ
    • ๋ณต์žกํ•œ ์‹œ๋‚˜๋ฆฌ์˜ค(API ์š”์ฒญ ์ทจ์†Œ, ์žฌ์‹œ๋„ ๋“ฑ) ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ
  • Redux Toolkit: ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ ์ฝ”๋“œ๋ฅผ ์ค„์ด๊ธฐ ์œ„ํ•œ ๊ณต์‹ ๋„๊ตฌ

๊ทธ๋Ÿฌ๋‚˜ Redux๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋‹จ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค:

 

  • ๋งŽ์€ ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ ์ฝ”๋“œ
  • ํ•™์Šต ๊ณก์„ ์ด ๊ฐ€ํŒŒ๋ฆ„
  • ์ž‘์€ ์ƒํƒœ ๋ณ€ํ™”์—๋„ ๋งŽ์€ ์ฝ”๋“œ ํ•„์š”

Recoil

Facebook์—์„œ ๋งŒ๋“  ์ƒํƒœ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค.

 

์žฅ์ 

  • React์™€์˜ ๋†’์€ ํ˜ธํ™˜์„ฑ
  • ๊ฐ„๋‹จํ•œ API (atom, selector)
  • ๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ๊ฐ€ ์‰ฌ์›€

๋‹จ์ 

  • ์•„ํ†ฐ์œผ๋กœ ๊ด€๋ฆฌ๋˜๋Š” ์ „์—ญ ์ƒํƒœ๋ฅผ ์–ด๋–ค ์ปดํฌ๋„ŒํŠธ๋ผ๋„ ๋ฐ”๋กœ ์•„ํ†ฐ์„ ๊ตฌ๋…ํ•ด์„œ ์—…๋ฐ์ดํŠธ๋ฅผ ๋ฐ›๊ธฐ์—, ์˜์กด์„ฑ์ด ์—ฌ๋Ÿฌ ๋ฐฉํ–ฅ์œผ๋กœ ์—ฎ์ด๋ฉด์„œ ์˜ˆ์ธกํ•˜๊ธฐ ํž˜๋“ค์–ด์ง‘๋‹ˆ๋‹ค.

Zustand

๊ฐ€๋ณ๊ณ  ์ง๊ด€์ ์ธ API๋ฅผ ์ œ๊ณตํ•˜๋Š” ํ˜„๋Œ€์ ์ธ ์ƒํƒœ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค.

 

์ฃผ์š” ํŠน์ง•

  • ์ตœ์†Œํ•œ์˜ ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ
  • TypeScript ์ง€์›์ด ํ›Œ๋ฅญํ•จ
  • Redux DevTools ์ง€์›
  • ๋ฏธ๋“ค์›จ์–ด ์‹œ์Šคํ…œ (persist, devtools ๋“ฑ)

์˜ˆ์‹œ ์ฝ”๋“œ:

import create from 'zustand'

interface PetStore {
  symptoms: Symptom[]
  addSymptom: (symptom: Symptom) => void
  foodLogs: FoodLog[]
  addFoodLog: (log: FoodLog) => void
}

const usePetStore = create<PetStore>((set) => ({
  symptoms: [],
  addSymptom: (symptom) => set((state) => ({
    symptoms: [...state.symptoms, symptom]
  })),
  foodLogs: [],
  addFoodLog: (log) => set((state) => ({
    foodLogs: [...state.foodLogs, log]
  }))
}))

 

3. ์‹ค์ œ ํ”„๋กœ์ ํŠธ์—์„œ์˜ ์„ ํƒ: Zustand

์•Œ๋ฆฌ๋‹ˆ ํ”„๋กœ์ ํŠธ์—์„œ์˜ ์š”๊ตฌ์‚ฌํ•ญ

  1. ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ ์—…๋ฐ์ดํŠธ
  2. ๋ณต์žกํ•œ ์ƒํƒœ ๊ด€๋ฆฌ (์•Œ๋ ˆ๋ฅด๊ธฐ ์ฆ์ƒ, ์Œ์‹ ๊ธฐ๋ก, ๋ถ„์„ ๋ฐ์ดํ„ฐ)
  3. ์„ฑ๋Šฅ ์ตœ์ ํ™”
  4. TypeScript ์ง€์›

Zustand ์„ ํƒ ์ด์œ 

  1. ๊ฐ„๋‹จํ•œ ์„ค์ •๊ณผ ์‚ฌ์šฉ๋ฒ•: ๋ณ„๋„์˜ Provider ์„ค์ •์ด ํ•„์š” ์—†๊ณ , ์ง๊ด€์ ์ธ API๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
  2. ์„ฑ๋Šฅ ์ตœ์ ํ™”: ํ•„์š”ํ•œ ์ƒํƒœ๋งŒ ๊ตฌ๋…ํ•˜์—ฌ ๋ถˆํ•„์š”ํ•œ ๋ฆฌ๋ Œ๋”๋ง์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.
  3. TypeScript ์ง€์›: ํƒ€์ž… ์ถ”๋ก ์ด ์ž˜ ๋™์ž‘ํ•˜์—ฌ ๊ฐœ๋ฐœ ์ƒ์‚ฐ์„ฑ์ด ํ–ฅ์ƒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
  4. ๋ฏธ๋“ค์›จ์–ด ์ง€์›: persist ๋ฏธ๋“ค์›จ์–ด๋ฅผ ํ†ตํ•ด ๋กœ์ปฌ ์Šคํ† ๋ฆฌ์ง€ ์—ฐ๋™์ด ์‰ฌ์› ์Šต๋‹ˆ๋‹ค.

Context API์™€์˜ ์กฐํ™”

Context API์™€ Zustand๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ €๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์—ญํ• ์„ ๊ตฌ๋ถ„ํ–ˆ์Šต๋‹ˆ๋‹ค:

  • Context API: ํ…Œ๋งˆ, ์ธ์ฆ ๋“ฑ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ „๋ฐ˜์ ์ธ ์„ค์ •๊ณผ ๊ด€๋ จ๋œ ์ƒํƒœ ๊ด€๋ฆฌ (๋‹จ์–ด ๊ทธ๋Œ€๋กœ ํŠน์ • ๋งฅ๋ฝ์„ ์”Œ์šธ๋•Œ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.)
  • Zustand: ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง๊ณผ ๊ด€๋ จ๋œ ๋ณต์žกํ•œ ์ƒํƒœ ๊ด€๋ฆฌ

 

4. ๊ฒฐ๋ก 

2024๋…„ ํ˜„์žฌ, ์ €๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ธฐ์ค€์œผ๋กœ ์ƒํƒœ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์„ ํƒํ–ˆ์Šต๋‹ˆ๋‹ค:

  1. ์ž‘์€ ํ”„๋กœ์ ํŠธ: Context API๋งŒ์œผ๋กœ๋„ ์ถฉ๋ถ„
  2. ์ค‘๊ฐ„ ๊ทœ๋ชจ ํ”„๋กœ์ ํŠธ: Zustand
  3. ๋Œ€๊ทœ๋ชจ ํ”„๋กœ์ ํŠธ: Redux Toolkit ๋˜๋Š” Zustand

์•Œ๋ฆฌ๋‹ˆ์™€ ๊ฐ™์ด ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ์™€ ๋ณต์žกํ•œ ์ƒํƒœ ๊ด€๋ฆฌ๊ฐ€ ํ•„์š”ํ•œ ํ”„๋กœ์ ํŠธ์—๋Š” Zustand๊ฐ€ ํŠนํžˆ ์ ํ•ฉํ–ˆ์Šต๋‹ˆ๋‹ค. ์ฝ”๋“œ๊ฐ€ ๊ฐ„๊ฒฐํ•˜๋ฉด์„œ๋„ ๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋ฉฐ, ํŠนํžˆ TypeScript์™€์˜ ํ˜ธํ™˜์„ฑ์ด ๋›ฐ์–ด๋‚˜ ๊ฐœ๋ฐœ ๊ฒฝํ—˜์ด ๋งค์šฐ ์ข‹์•˜์Šต๋‹ˆ๋‹ค.

 

 

 

 

ํ”„๋ฆฌํ”ฝ์Šค!!

 

ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์„ ํ•˜๋‹ค ๋ณด๋ฉด ํฌ๋กœ์Šค๋ธŒ๋ผ์šฐ์ง• ์ด์Šˆ๋Š” ํ”ผํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

::-webkit-input-placeholder์™€ ๊ฐ™์€ ๋ธŒ๋ผ์šฐ์ €๋ณ„ ํ”„๋ฆฌํ”ฝ์Šค(-webkit-, -moz-, -ms- ๋“ฑ)๋ฅผ ์ผ์ผ์ด ์ถ”๊ฐ€ํ•˜๋Š” ๊ฑด ๊ต‰์žฅํžˆ ๋ฒˆ๊ฑฐ๋กœ์šด ์ž‘์—…์ด์˜ˆ์š”.

 

์‚ฌ์‹ค ํ˜„์—…์—์„œ๋Š” ๊ฐœ๋ฐœ์ž๊ฐ€ ์ง์ ‘ ์ด๋Ÿฌํ•œ ํ”„๋ฆฌํ”ฝ์Šค๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์ด ๋งค์šฐ ๊ณค๋ž€ํ•œ ์ผ์ž…๋‹ˆ๋‹ค.

(์ €๋„ ๊ด€๋ จ๋œ ๋Œ€ํ™”๋ฅผ ๋‚˜๋ˆ„๋‹ค ์•Œ๊ฒŒ๋์–ด์š”!)

 

์ด๋Ÿฌํ•œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ฒˆ๋“ค๋Ÿฌ๋ฅผ ํ†ตํ•ด ์ž๋™์œผ๋กœ ํ”„๋ฆฌํ”ฝ์Šค๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ๋Š” ๋ฐฉ๋ฒ•์ธ Autoprefixer๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

PostCSS๋ฅผ ๊ธฐ๋ณธ์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ผ๋ฉด, ํ˜น์€ CSS๋กœ ๋ณ€ํ™˜ํ•œ ํ›„ postcss-loader๊ฐ€ ์ฒ˜๋ฆฌํ•˜๊ฒŒ ํ•œ๋‹ค๋ฉด ๊ฐœ๋ฐœ์ž๊ฐ€ ๊ฐ ๋ธŒ๋ผ์šฐ์ € ํ™˜๊ฒฝ์— ๋งž๊ฒŒ ์Šคํƒ€์ผ์„ ๋”ฐ๋กœ ์ž‘์„ฑํ•˜์ง€ ์•Š์•„๋„ Autoprefixer๊ฐ€ ์›นํ‚ท ํ”„๋ฆฌํ”ฝ์Šค์™€ ๊ฐ™์€ ๊ฒƒ์„ ์ž๋™์œผ๋กœ ๋ถ™์—ฌ์ค๋‹ˆ๋‹ค.

 

์ด๋ฒˆ ํฌ์ŠคํŠธ์—์„œ๋Š” PostCSS์™€ Autoprefixer๋ฅผ ํ™œ์šฉํ•ด ํ•ด๋‹น ISSUE๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์‹ค์ œ ํ”„๋กœ์ ํŠธ ๊ฒฝํ—˜์„ ๋ฐ”ํƒ•์œผ๋กœ ์ƒ์„ธํžˆ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค!

 

PostCSS์™€ Autoprefixer๋ž€?

PostCSS

  • JavaScript ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์‚ฌ์šฉํ•ด CSS๋ฅผ ๋ณ€ํ™˜ํ•˜๋Š” ๋„๊ตฌ
  • "Post-processor"๋ผ๊ณ ๋„ ๋ถˆ๋ฆฌ๋Š” ์ด์œ ๋Š” CSS๋ฅผ ์ž‘์„ฑํ•œ ํ›„์— ์ฒ˜๋ฆฌํ•˜๊ธฐ ๋•Œ๋ฌธ
  • CSS ๊ตฌ๋ฌธ ๋ถ„์„์— ์ตœ์ ํ™”๋œ ํŒŒ์„œ๋ฅผ ์‚ฌ์šฉํ•ด ๋†’์€ ์„ฑ๋Šฅ์„ ๋ณด์ž„
  • CSS ์ „์ฒ˜๋ฆฌ๊ธฐ(Sass)๋‚˜ ํ›„์ฒ˜๋ฆฌ๊ธฐ๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅ (์ด ๋ถ€๋ถ„์€ ์ œ๊ฐ€ ํ—ท๊ฐˆ๋ ธ๋˜ ๋ถ€๋ถ„์ด๋ผ ์•„๋ž˜ ์ถ”๊ฐ€์„ค๋ช…์„ ๋„ฃ์—ˆ์–ด์š”!)
    1. CSS ์ „์ฒ˜๋ฆฌ๊ธฐ(SASS):
      CSS ๊ธฐ๋Šฅ์„ ํ™•์žฅํ•œ ์–ธ์–ด ์ž…๋‹ˆ๋‹ค. ๋ณ€์ˆ˜, ์ค‘์ฒฉ ๊ทœ์น™, ๋ฏน์Šค์ธ ๋“ฑ์„ ์ง€์›ํ•˜๊ณ , PostCSS๋Š” Sass๋กœ ์ž‘์„ฑํ•œ ์ฝ”๋“œ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, PostCSS ์ž์ฒด๋Š” Sass ๋ฌธ๋ฒ•์„ ์‚ฌ์šฉํ•˜์ง„ ์•Š์•„์š”.
    2. CSS ํ›„์ฒ˜๋ฆฌ๊ธฐ:
      PostCSS๋Š” CSS์ฝ”๋“œ๊ฐ€ ์ž‘์„ฑ๋œ ํ›„์— ์ถ”๊ฐ€์ ์ธ ๋ณ€ํ™˜ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋„๊ตฌ๋กœ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
      ์˜ˆ๋ฅผ ๋“ค์–ด, ์ œ๊ฐ€ ์ง€๊ธˆ ์ž‘์„ฑํ•˜๋Š” Autoprefixer ๊ฐ™์€ ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์‚ฌ์šฉํ•ด์„œ ์ž๋™์œผ๋กœ ๋ธŒ๋ผ์šฐ์ € ํ”„๋ฆฌํ”ฝ์Šค๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ฒ˜๋Ÿผ์š”!
  • 200๊ฐœ ์ด์ƒ์˜ ํ”Œ๋Ÿฌ๊ทธ์ธ์œผ๋กœ ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ ์ œ๊ณต (์ƒํƒœ๊ณ„๊ฐ€ ํฝ๋‹ˆ๋‹ค!)
  • ๋ชจ๋“ˆํ™”๋œ ๊ตฌ์กฐ๋กœ ํ•„์š”ํ•œ ๊ธฐ๋Šฅ๋งŒ ํ”Œ๋Ÿฌ๊ทธ์ธ์œผ๋กœ ์„ ํƒ์  ์ถ”๊ฐ€ ๊ฐ€๋Šฅ

Autoprefixer

  • PostCSS์˜ ๋Œ€ํ‘œ์ ์ธ ํ”Œ๋Ÿฌ๊ทธ์ธ
  • ๋ธŒ๋ผ์šฐ์ € ํ”„๋ฆฌํ”ฝ์Šค๋ฅผ ์ž๋™์œผ๋กœ ์ถ”๊ฐ€ํ•ด์ฃผ๋Š” ๋„๊ตฌ
  • Browserslist ์„ค์ •์„ ํ†ตํ•ด ์ง€์› ๋ธŒ๋ผ์šฐ์ € ๋ฒ”์œ„ ์ง€์ • ๊ฐ€๋Šฅ

 

Create React App๊ณผ Vite์˜ ๊ธฐ๋ณธ ์ง€์›

ํ•ด๋‹น ๋ถ€๋ถ„์€ Webpack์„ ์ง์ ‘ ์„ค์ •ํ•˜์ง€ ์•Š์„ ๋• ์–ด๋–ป๊ฒŒ ๋˜๋Š” ๊ฑด์ง€, ๊ธฐ๋ณธ ์„ค์ •์ด ๋˜์–ด ์žˆ๋Š”์ง€ ๋“ฑ์ด ๊ถ๊ธˆํ•ด ์ฐพ์•„๋ณด๊ฒŒ ๋์Šต๋‹ˆ๋‹ค.

Create React App (CRA)

  • PostCSS์™€ Autoprefixer๊ฐ€ ๊ธฐ๋ณธ์œผ๋กœ ์„ค์ •๋˜์–ด ์žˆ์Œ
  • ๋ณ„๋„ ์„ค์ • ์—†์ด ๋ฐ”๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
  • browserslist ์„ค์ •๋„ ๊ธฐ๋ณธ ์ œ๊ณต

CRA์˜ package.json์„ ๋ณด์‹œ๋ฉด ์•„๋ž˜์™€ ๊ฐ™์€ ์ฝ”๋“œ๋ฅผ ํ™•์ธํ•˜์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

"browserslist": {
  "production": [
    ">0.2%",
    "not dead",
    "not op_mini all"
  ],
  "development": [
    "last 1 chrome version",
    "last 1 firefox version",
    "last 1 safari version"
  ]
}


Vite

  • PostCSS๋Š” ๊ธฐ๋ณธ ์ง€์›
  • Autoprefixer๋Š” ๋ณ„๋„ ์„ค์น˜ ํ•„์š”
yarn add -D autoprefixer
// vite.config.js
import autoprefixer from 'autoprefixer'

export default {
  css: {
    postcss: {
      plugins: [
        autoprefixer()
      ]
    }
  }
}

 

ํ˜„์žฌ ํ”„๋กœ์ ํŠธ์— Custom Webpack ์„ค์ •ํ•˜๊ธฐ

์ปค์Šคํ…€ Webpack ์„ค์ •์—์„œ๋Š” ์ˆ˜๋™์œผ๋กœ PostCSS์™€ Autoprefixer๋ฅผ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

ํ•„์š”ํ•œ ํŒจํ‚ค์ง€ ์„ค์น˜

yarn add -D postcss postcss-loader autoprefixer


์›นํŒฉ ์„ค์ • ์˜ˆ์‹œ

module.exports = {
  module: {
    rules: [
      {
        test: /\.module\.scss$/,
        use: [
          "style-loader",
          {
            loader: "css-loader",
            options: {
              modules: {
                localIdentName: "[name]__[local]--[hash:base64:5]",
              },
              importLoaders: 2,
            },
          },
          {
            loader: "postcss-loader",
            options: {
              postcssOptions: {
                plugins: [
                  ["autoprefixer"]
                ],
              },
            },
          },
          "sass-loader",
        ],
      },
      {
        test: /\.(scss|css)$/,
        exclude: /\.module\.scss$/,
        use: [
          "style-loader",
          {
            loader: "css-loader",
            options: {
              importLoaders: 2,
            },
          },
          {
            loader: "postcss-loader",
            options: {
              postcssOptions: {
                plugins: [
                  ["autoprefixer"]
                ],
              },
            },
          },
          "sass-loader",
        ],
      },
    ],
  },
  // ... ๊ธฐํƒ€ ์›นํŒฉ ์„ค์ •
};

 

์ถ”๊ฐ€์ ์œผ๋กœ ์ €๋Š” ์„ค์ •ํ•˜์ง€ ์•Š์•˜์ง€๋งŒ ๋‹ค๋ฅธ ๋ธ”๋กœ๊ทธ ๊ธ€์„ ๋ณด์‹œ๋ฉด ๊ฐ„ํ˜น postcss-scss์— ๋Œ€ํ•œ ์ถ”๊ฐ€๊ฐ€ ๋ณด์ด์…จ์„ ํ…๋ฐ, ์ด๊ฑด ๋ฌด์Šจ ์—ญํ• ์ผ๊นŒ์š”?!

postcss-scss๋ž€?

  • ์ผ๋ฐ˜์ ์œผ๋กœ ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค
  • sass-loader๊ฐ€ SCSS๋ฅผ CSS๋กœ ๋ณ€ํ™˜ํ•œ ํ›„ postcss-loader๊ฐ€ ์ฒ˜๋ฆฌํ•˜๊ธฐ ๋•Œ๋ฌธ
  • postcss-scss๋Š” SCSS ๊ตฌ๋ฌธ์„ ์ง์ ‘ PostCSS ํ”Œ๋Ÿฌ๊ทธ์ธ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๊ณ  ์‹ถ์„ ๋•Œ๋งŒ ํ•„์š”

 

์‹ค์ˆ˜ํ–ˆ๋˜ ๋ถ€๋ถ„๊ณผ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

package.json์— ์ž‘์„ฑ๋ผ์•ผ ํ•˜๋Š” json ๋ฌธ๋ฒ•์„. browserslistrc ํŒŒ์ผ์— ์ ์šฉํ•ด ๋ฒ„๋ ธ์–ด์š”. ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ์ฝ๋‹ค ์ˆœ๊ฐ„์ ์œผ๋กœ ์ฐฉ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค. ๋„ˆ๋ฌด ๊ฐ„๋‹จํ•˜์ง€๋งŒ ์ œ ๊ธฐ์ค€์—์„œ ์น˜๋ช…์ ์ธ ์‹ค์ˆ˜๋ž€ ์ƒ๊ฐ์ด ๋“ค์–ด ๊ธฐ๋กํ•ด๋‘๋ ค ํ•ฉ๋‹ˆ๋‹ค!

Browserslist ์„ค์ • ์˜ค๋ฅ˜

Error [BrowserslistError]: Unknown browser query `{`. Maybe you are using old Browserslist or made typo in query.

 

 

.browserslistrc ํŒŒ์ผ ์‚ฌ์šฉ (๊ถŒ์žฅ)

์ €๋Š” ์ตœ์ข…์ ์œผ๋กœ ์•„๋ž˜์˜ ์ฝ”๋“œ๋กœ ์ˆ˜์ •ํ–ˆ์Šต๋‹ˆ๋‹ค!

# ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ
[production]
>0.2% # ์ „ ์„ธ๊ณ„ ์‚ฌ์šฉ๋ฅ  0.2% ์ด์ƒ์ธ ๋ธŒ๋ผ์šฐ์ €
not dead # ๊ณต์‹ ์ง€์›์ด ์ค‘๋‹จ๋˜์ง€ ์•Š์€ ๋ธŒ๋ผ์šฐ์ €
not op_mini all # Opera Mini ๋ธŒ๋ผ์šฐ์ € ์ œ์™ธ

# ๊ฐœ๋ฐœ ํ™˜๊ฒฝ
[development]
last 1 chrome version # ์ตœ์‹  ํฌ๋กฌ ๋ฒ„์ „
last 1 firefox version # ์ตœ์‹  ํŒŒ์ด์–ดํญ์Šค ๋ฒ„์ „
last 1 safari version # ์ตœ์‹  ์‚ฌํŒŒ๋ฆฌ ๋ฒ„์ „


ํ•ด๊ฒฐ๋ฐฉ๋ฒ• 2: package.json์— ์„ค์ •

{
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}


Browserslist ์„ค์ • ๊ฐ€์ด๋“œ

์ฃผ์š” ์ฟผ๋ฆฌ ์„ค๋ช…

  • >0.2%: ์ „ ์„ธ๊ณ„ ์‚ฌ์šฉ๋ฅ  0.2% ์ด์ƒ์ธ ๋ธŒ๋ผ์šฐ์ €
  • not dead: ๊ณต์‹ ์ง€์›์ด ์ค‘๋‹จ๋˜์ง€ ์•Š์€ ๋ธŒ๋ผ์šฐ์ €
  • not op_mini all: Opera Mini ๋ธŒ๋ผ์šฐ์ € ์ œ์™ธ
  • last 1 chrome version: ์ตœ์‹  ํฌ๋กฌ ๋ฒ„์ „
  • last 2 versions: ๊ฐ ๋ธŒ๋ผ์šฐ์ €์˜ ์ตœ์‹  2๊ฐœ ๋ฒ„์ „
  • > 1%: ์ „ ์„ธ๊ณ„ ์ ์œ ์œจ 1% ์ด์ƒ
  • IE 11: IE 11 ์ง€์›

์„ค์ • ํ…Œ์ŠคํŠธ

  • browserslist.dev์—์„œ ์„ค์ •์„ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์Œ
  • ์–ด๋–ค ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ํฌํ•จ๋˜๋Š”์ง€ ์‹ค์‹œ๊ฐ„์œผ๋กœ ํ™•์ธ ๊ฐ€๋Šฅ

 

์‹ค์ œ ๋™์ž‘ ์˜ˆ์‹œ

๋ณ€ํ™˜ ์ „ CSS

.example {
  display: flex;
  user-select: none;
}

๋ณ€ํ™˜ ํ›„ CSS

.example {
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-user-select: none;
     -moz-user-select: none;
      -ms-user-select: none;
          user-select: none;
}

 

์ฃผ์˜์‚ฌํ•ญ๊ณผ ํŒ!

  • ๋กœ๋” ์ˆœ์„œ: PostCSS ๋กœ๋”๋Š” Sass ๋กœ๋” ์ดํ›„, CSS ๋กœ๋” ์ด์ „์— ์œ„์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.  
    1. sass-loader (SCSS -> CSS ๋ณ€ํ™˜)
    2. postcss-loader (์ž๋™ ํ”„๋ฆฌํ”ฝ์Šค ์ถ”๊ฐ€)
    3. css-loader (CSS -> JS ๋ณ€ํ™˜)
    4. style-loader (JS -> style ํƒœ๊ทธ ์‚ฝ์ž…)
  • ์„ฑ๋Šฅ ์ตœ์ ํ™”: development ํ™˜๊ฒฝ์—์„œ๋Š” source map์„ ํ™œ์„ฑํ™”ํ•˜๊ณ , production์—์„œ๋Š” ๋น„ํ™œ์„ฑํ™”ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
  • ๋ธŒ๋ผ์šฐ์ € ์ง€์› ๋ฒ”์œ„: browserslist ์„ค์ •์„ ํ†ตํ•ด ์ง€์›ํ•  ๋ธŒ๋ผ์šฐ์ € ๋ฒ”์œ„๋ฅผ ์ ์ ˆํžˆ ์กฐ์ ˆํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

 

์ฃผ์˜์‚ฌํ•ญ๊ณผ ํŒ์—์„œ์˜ Development ํ™˜๊ฒฝ์—์„œ source map ํ™œ์„ฑํ™”ํ•˜๋Š” ์ด์œ  & Production ํ™˜๊ฒฝ์—์„œ ๋น„ํ™œ์„ฑํ™” ํ•˜๋Š” ์ด์œ ์— ๋Œ€ํ•ด์„œ๋งŒ ์ข€ ๋” ์ž์„ธํžˆ ์•Œ์•„๋ณผ๊ฒŒ์š”!

 

Development ํ™˜๊ฒฝ์—์„œ source map ํ™œ์„ฑํ™”ํ•˜๋Š” ์ด์œ :

  1. ๋””๋ฒ„๊น… ์šฉ์ด์„ฑ
    • ๊ฐœ๋ฐœ ์ค‘์—๋Š” ์›๋ณธ ์†Œ์Šค ์ฝ”๋“œ์™€ ๋ณ€ํ™˜๋œ ์ฝ”๋“œ๋ฅผ ๋งคํ•‘ํ•˜์—ฌ ์‰ฝ๊ฒŒ ๋””๋ฒ„๊น…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
    • ๋ธŒ๋ผ์šฐ์ €์˜ ๊ฐœ๋ฐœ์ž ๋„๊ตฌ์—์„œ ์‹ค์ œ ์ž‘์„ฑํ•œ CSS ํŒŒ์ผ์˜ ์œ„์น˜์™€ ๋ผ์ธ์„ ์ •ํ™•ํžˆ ํ™•์ธ ๊ฐ€๋Šฅ
    • PostCSS๋กœ ๋ณ€ํ™˜๋œ ์ฝ”๋“œ๊ฐ€ ์•„๋‹Œ ์›๋ณธ ์ฝ”๋“œ๋ฅผ ์ง์ ‘ ํ™•์ธํ•˜๋ฉฐ ์ˆ˜์ • ๊ฐ€๋Šฅ
  2. ๊ฐœ๋ฐœ ํšจ์œจ์„ฑ
    • ์ฝ”๋“œ ์ˆ˜์ • ์‹œ ์ฆ‰๊ฐ์ ์ธ ํ”ผ๋“œ๋ฐฑ ํ™•์ธ ๊ฐ€๋Šฅ
    • CSS ๋ฌธ์ œ ๋ฐœ์ƒ ์‹œ ์ •ํ™•ํ•œ ์œ„์น˜ ์ถ”์ ์ด ๊ฐ€๋Šฅํ•˜์—ฌ ์ˆ˜์ • ์‹œ๊ฐ„ ๋‹จ์ถ•
    • ๋ณต์žกํ•œ ์Šคํƒ€์ผ ๊ตฌ์กฐ์—์„œ๋„ ์›๋ณธ ์ฝ”๋“œ ์œ„์น˜๋ฅผ ์‰ฝ๊ฒŒ ํŒŒ์•…!

Production ํ™˜๊ฒฝ์—์„œ source map ๋น„ํ™œ์„ฑํ™”ํ•˜๋Š” ์ด์œ :

  1. ํŒŒ์ผ ํฌ๊ธฐ ์ตœ์ ํ™”
    • source map ํŒŒ์ผ์€ ์›๋ณธ ์ฝ”๋“œ์™€ ๋ณ€ํ™˜๋œ ์ฝ”๋“œ์˜ ๋งคํ•‘ ์ •๋ณด๋ฅผ ํฌํ•จํ•˜๋ฏ€๋กœ ์ƒ๋‹นํ•œ ํฌ๊ธฐ๋ฅผ ์ฐจ์ง€
    • ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ๋ถˆํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ ์ „์†ก์„ ์ค„์—ฌ ๋กœ๋”ฉ ์†๋„ ํ–ฅ์ƒ
    • ์ผ๋ฐ˜์ ์œผ๋กœ source map ํŒŒ์ผ์€ ์›๋ณธ ํŒŒ์ผ ํฌ๊ธฐ์˜ ์ ˆ๋ฐ˜ ์ด์ƒ์„ ์ฐจ์ง€ํ•  ์ˆ˜ ์žˆ์Œ
  2. ๋ณด์•ˆ ๊ฐ•ํ™”
    • source map์ด ๋…ธ์ถœ๋˜๋ฉด ์›๋ณธ ์ฝ”๋“œ ๊ตฌ์กฐ๊ฐ€ ๋“œ๋Ÿฌ๋‚  ์ˆ˜ ์žˆ์Œ
    • ์•…์˜์ ๋ฅผ ๊ฐ€์ง„ ์‚ฌ์šฉ์ž๊ฐ€ ์ฝ”๋“œ ๊ตฌ์กฐ๋ฅผ ๋ถ„์„ํ•˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€
    • ๊ธฐ์—…์˜ proprietary ์ฝ”๋“œ ๋ณดํ˜ธ!!
  3. ์„ฑ๋Šฅ ์ตœ์ ํ™”
    • ๋ธŒ๋ผ์šฐ์ €๊ฐ€ source map์„ ์ฒ˜๋ฆฌํ•˜๋Š” ์ถ”๊ฐ€์ ์ธ ์ž‘์—… ์ œ๊ฑฐ
    • ์ดˆ๊ธฐ ํŽ˜์ด์ง€ ๋กœ๋”ฉ ์‹œ๊ฐ„ ๋‹จ์ถ•
    • ์„œ๋ฒ„ ๋Œ€์—ญํญ ์‚ฌ์šฉ๋Ÿ‰ ๊ฐ์†Œ

์‹ค์ œ ์„ค์ • ์˜ˆ์‹œ! (ํ”„๋กœ์ ํŠธ ์ฝ”๋“œ๋ณด๋‹ค ๊ฐ„๋žตํ™”)

// webpack.config.js
module.exports = {
  mode: process.env.NODE_ENV,
  module: {
    rules: [{
      test: /\.css$/,
      use: [
        'style-loader',
        {
          loader: 'css-loader',
          options: {
            sourceMap: process.env.NODE_ENV === 'development'
          }
        },
        {
          loader: 'postcss-loader',
          options: {
            sourceMap: process.env.NODE_ENV === 'development'
          }
        }
      ]
    }]
  }
};

 

 

๊ฒฐ๋ก 

PostCSS์™€ Autoprefixer๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํฌ๋กœ์Šค๋ธŒ๋ผ์šฐ์ง•์„ ์œ„ํ•œ ์ˆ˜๋™ ์ž‘์—…์„ ์ž๋™ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

CRA๋‚˜ Create Vite๊ฐ™์€ ๊ฒฝ์šฐ ์ด๋ฏธ ์ด๋Ÿฌํ•œ ๊ธฐ๋Šฅ์„ ๊ธฐ๋ณธ์œผ๋กœ ์ œ๊ณตํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ์ปค์Šคํ…€ ์›นํŒฉ ์„ค์ •์—์„œ๋„ ๊ฐ„๋‹จํ•œ ์„ค์ •๋งŒ์œผ๋กœ ๋™์ผํ•œ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

ํŠนํžˆ Browserslist ์„ค์ •์„ ํ†ตํ•ด ํ”„๋กœ์ ํŠธ์˜ ๋ธŒ๋ผ์šฐ์ € ์ง€์› ๋ฒ”์œ„๋ฅผ ๋ช…ํ™•ํžˆ ํ•˜๊ณ , ํ•„์š”ํ•œ ํ”„๋ฆฌํ”ฝ์Šค๋งŒ ์ถ”๊ฐ€ํ•จ์œผ๋กœ์จ ์ตœ์ ํ™”๋œ CSS๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๊ฒ ์ฃ ?!

 

.browserslistrc ํŒŒ์ผ์„ ์‚ฌ์šฉํ•˜๋ฉด ์„ค์ • ๊ด€๋ฆฌ๊ฐ€ ๋”์šฑ ์šฉ์ดํ•ด์ง€๋ฉฐ, ๋‹ค๋ฅธ ๋„๊ตฌ๋“ค๊ณผ๋„ ์„ค์ •์„ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

์—ฌ๊ธฐ์„œ "๋‹ค๋ฅธ ๋„๊ตฌ๋“ค"์€ ์ฃผ๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์€ CSS ๋˜๋Š” JavaScript ๋„๊ตฌ๋“ค์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค!

  1. Autoprefixer: CSS์— ํ•„์š”ํ•œ ๋ธŒ๋ผ์šฐ์ € ํ”„๋ฆฌํ”ฝ์Šค๋ฅผ ์ž๋™์œผ๋กœ ์ถ”๊ฐ€ํ•ด์ฃผ๋Š” ๋„๊ตฌ๋กœ, Browserslist ์„ค์ •์„ ํ†ตํ•ด ์–ด๋–ค ํ”„๋ฆฌํ”ฝ์Šค๋ฅผ ์ถ”๊ฐ€ํ• ์ง€ ๊ฒฐ์ • (์ง€๊ธˆ๊นŒ์ง€ ์„ค๋ช…๋“œ๋ฆฐ ๋‚ด์šฉ์ด์ฃ ?!)
  2. Babel: ์ตœ์‹  JavaScript ์ฝ”๋“œ๋ฅผ ๊ตฌํ˜• ๋ธŒ๋ผ์šฐ์ €์—์„œ๋„ ์ž‘๋™ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ณ€ํ™˜ํ•ด์ฃผ๋Š” ๋„๊ตฌ๋กœ, Browserslist ์„ค์ •์„ ํ†ตํ•ด ์ง€์›ํ•  ๋ธŒ๋ผ์šฐ์ € ์ •์˜ ๊ฐ€๋Šฅ
  3. ESLint: JavaScript ์ฝ”๋“œ์˜ ํ’ˆ์งˆ์„ ๊ด€๋ฆฌํ•˜๋Š” ๋„๊ตฌ๋กœ, Browserslist์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•ด ํŠน์ • ๋ธŒ๋ผ์šฐ์ € ํ™˜๊ฒฝ์— ๋งž๋Š” ์ฝ”๋“œ ์Šคํƒ€์ผ ์ ์šฉ ๊ฐ€๋Šฅ
  4. Stylelint: CSS ์Šคํƒ€์ผ์„ ๊ฒ€์‚ฌํ•˜๋Š” ๋„๊ตฌ๋กœ, Browserslist ์„ค์ •์„ ์ฐธ๊ณ ํ•˜์—ฌ ๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜์„ฑ ๊ด€๋ จ ๊ทœ์น™ ์ ์šฉ ๊ฐ€๋Šฅ

์ฆ‰, .browserslistrc ํŒŒ์ผ์„ ์‚ฌ์šฉํ•˜๋ฉด ์ด๋Ÿฌํ•œ ๋‹ค์–‘ํ•œ ๋„๊ตฌ๋“ค์ด ๋™์ผํ•œ ๋ธŒ๋ผ์šฐ์ € ์ง€์› ๋ฒ”์œ„๋ฅผ ๊ณต์œ ํ•˜๊ฒŒ ๋˜์–ด, ๊ฐœ๋ฐœ ๋ฐ ์œ ์ง€๋ณด์ˆ˜ ๊ณผ์ •์—์„œ ์ผ๊ด€์„ฑ์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!

 

 

๐Ÿ“ƒ ์ฐธ๊ณ  ๋ฌธํ—Œ  
PostCSS ๊ณต์‹ ๋ฌธ์„œ
Autoprefixer GitHub
Browserslist
Create React App PostCSS ์„ค์ •
Vite CSS ์„ค์ •
Webpack 5 postcss-loader 
Browserslist ๊ด€๋ จ ๊ฐœ์ธ ๋ธ”๋กœ๊ทธ

 

+ Recent posts