๐Ÿ—‚๏ธ pnpm workspace & filtering โ€” ๋ชจ๋…ธ๋ ˆํฌ์˜ ์™•

2026๋…„ 3์›” 20์ผ ์ˆ˜์ •๋จ

๐Ÿ“‹ ๊ฐœ์š”

pnpm-workspace.yaml ์„ค์ •๋ถ€ํ„ฐ --filter ์…€๋ ‰ํ„ฐ ๋ฌธ๋ฒ•, workspace: ํ”„๋กœํ† ์ฝœ, ํŒจํ‚ค์ง€ ๊ฐ„ ์˜์กด์„ฑ๊นŒ์ง€ โ€” ๋ชจ๋…ธ๋ ˆํฌ๋ฅผ pnpm ์œผ๋กœ ์™„์ „ ์ •๋ณตํ•œ๋‹ค

04. workspace & filtering

๐Ÿ“‹ ๋ชฉ์ฐจ

  1. ์ด์ •๋ฆฌ
  2. ๋งˆ๋ฌด๋ฆฌ ํ€ด์ฆˆ
  3. ์˜์ฒ ์ด์˜ ํ‡ด๊ทผ ์ผ๊ธฐ
  4. ๋” ์•Œ์•„๋ณด๊ธฐ

๐Ÿ“Œ ์ด ๋ฌธ์„œ๋ฅผ ์ฝ๊ธฐ ์ „์—

โฑ๏ธ ์˜ˆ์ƒ ์ฝ๊ธฐ ์‹œ๊ฐ„: ์•ฝ 30๋ถ„(์ „์ฒด) / ํ•ต์‹ฌ ํŒŒํŠธ๋งŒ: 15๋ถ„

๐Ÿ—บ๏ธ ์ด ๋ฌธ์„œ์˜ ํ๋ฆ„

[workspace.yaml ๊ตฌ์„ฑ] โ†’ [workspace: ํ”„๋กœํ† ์ฝœ] โ†’ [--filter ์…€๋ ‰ํ„ฐ] โ†’ [recursive ๋ช…๋ น] โ†’ [์‹ค์ „ ๋ชจ๋…ธ๋ ˆํฌ ๊ตฌ์„ฑ]

๐ŸŽฏ ์ด ๋ฌธ์„œ๋ฅผ ๋‹ค ์ฝ์œผ๋ฉด ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ

  • pnpm-workspace.yaml ๋กœ ๋ชจ๋…ธ๋ ˆํฌ๋ฅผ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค
  • workspace: ํ”„๋กœํ† ์ฝœ๋กœ ๋‚ด๋ถ€ ํŒจํ‚ค์ง€๋ฅผ ์„œ๋กœ ์˜์กด์„ฑ์œผ๋กœ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค
  • --filter ์…€๋ ‰ํ„ฐ๋กœ ํŠน์ • ํŒจํ‚ค์ง€์—๋งŒ ๋ช…๋ น์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค
  • CI ์—์„œ ๋ณ€๊ฒฝ๋œ ํŒจํ‚ค์ง€๋งŒ ๋นŒ๋“œ/ํ…Œ์ŠคํŠธํ•˜๋Š” ํŒŒ์ดํ”„๋ผ์ธ์„ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค

๐Ÿ—บ๏ธ ์ด ๋ฌธ์„œ์˜ ๋ฐฐ๊ฒฝ ์„ธ๊ณ„๊ด€: '์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ'

๋ชจ๋…ธ๋ ˆํฌ์— ๋‹นํ™ฉํ•œ ์˜์ฒ ์ด์™€ pnpm-workspace.yaml์„ ๋ณด์—ฌ์ฃผ๋Š” ์˜ํ˜ธ
  • ๐Ÿฃ ์˜์ฒ  ( ์‹ ์ž… ): "์˜์ˆ˜ ๋‹˜์ด ๊ฐ‘์ž๊ธฐ ์šฐ๋ฆฌ ํ”„๋กœ์ ํŠธ๋ฅผ ๋ชจ๋…ธ๋ ˆํฌ๋กœ ๋ฐ”๊พธ์ž๊ณ  ํ•˜์…จ์–ด์š”. ํ”„๋ก ํŠธ์—”๋“œ, ๋ฐฑ์—”๋“œ, ๊ณตํ†ต UI ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ•˜๋‚˜์˜ ๋ ˆํฌ์— ํ•ฉ์นœ๋Œ€์š”. ๊ทผ๋ฐ ์ € ๋ชจ๋…ธ๋ ˆํฌ ์„ค์ •ํ•ด๋ณธ ์ ์ด ์—†์–ด์„œ ๋ง‰๋ง‰ํ•ด์š”. Turborepo ๋„ ์จ์•ผ ํ•˜๋‚˜์š”? Lerna ๋Š”์š”? pnpm ๋งŒ์œผ๋กœ ๋ชจ๋…ธ๋ ˆํฌ๊ฐ€ ๋˜๋Š” ๊ฑด๊ฐ€์š”? ๊ณตํ†ต ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ”„๋ก ํŠธ์—”๋“œ์—์„œ ์“ฐ๋ ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ํ•ด์š”?"
  • ๐Ÿฆ ์˜ํ˜ธ ( ๋ฆฌ๋“œ ): "์˜์ฒ  ๋‹˜, pnpm ์ž์ฒด๊ฐ€ ์ด๋ฏธ ๊ฐ•๋ ฅํ•œ ๋ชจ๋…ธ๋ ˆํฌ ๋„๊ตฌ์˜ˆ์š”. pnpm-workspace.yaml ํ•˜๋‚˜๋กœ ์‹œ์ž‘ํ•˜๊ณ , ํŒจํ‚ค์ง€ ๊ฐ„ ์˜์กด์„ฑ์€ workspace: ํ”„๋กœํ† ์ฝœ๋กœ ์—ฐ๊ฒฐํ•ด์š”. Turborepo ๋Š” ๋นŒ๋“œ ์บ์‹ฑ์„ ์œ„ํ•ด ๋‚˜์ค‘์— ์ถ”๊ฐ€ํ•˜๋ฉด ๋˜๊ณ ์š”. --filter ๋กœ ํŠน์ • ํŒจํ‚ค์ง€์—๋งŒ ๋ช…๋ น์„ ์‹คํ–‰ํ•˜๊ณ , -r ๋กœ ์ „์ฒด๋ฅผ ์žฌ๊ท€์ ์œผ๋กœ ๋Œ๋ฆด ์ˆ˜ ์žˆ์–ด์š”. ์ด๊ฒƒ๋ถ€ํ„ฐ ์ œ๋Œ€๋กœ ์•Œ๊ณ  ๋‚˜๋ฉด Turborepo ๋„ ๊ธˆ๋ฐฉ ์ดํ•ด๋ผ์š”."

๐Ÿค” ์™œ ์•Œ์•„์•ผ ํ•˜๋Š”๊ฐ€

๊ทœ๋ชจ๊ฐ€ ์ปค์ง„ ํ”„๋กœ์ ํŠธ๋Š” ํ•„์—ฐ์ ์œผ๋กœ ๋ชจ๋…ธ๋ ˆํฌ ๋ฅผ ๊ณ ๋ฏผํ•˜๊ฒŒ ๋œ๋‹ค.

์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ๊ฐ€ ์ฒ˜์Œ์—” ๋‹จ์ผ Next.js ์•ฑ์ด์—ˆ์ง€๋งŒ, ์ง€๊ธˆ์€ ๋‹ค์Œ์ด ํ•„์š”ํ•ด์กŒ๋‹ค:

ํ˜„์žฌ ์ƒํ™ฉ:
โ”œโ”€โ”€ youngsu-community (Next.js ํ”„๋ก ํŠธ์—”๋“œ)
โ”œโ”€โ”€ youngsu-admin (Next.js ์–ด๋“œ๋ฏผ)
โ”œโ”€โ”€ youngsu-api (NestJS ๋ฐฑ์—”๋“œ)

๋ฌธ์ œ:
- ๊ณตํ†ต ํƒ€์ž… ์ •์˜ (User, Post, Comment ๋“ฑ) ๊ฐ€ ์„ธ ๊ณณ์—์„œ ์ค‘๋ณต
- ๊ณตํ†ต UI ์ปดํฌ๋„ŒํŠธ (Button, Card ๋“ฑ) ๋ฅผ ๋ชจ๋‘๊ฐ€ ๋”ฐ๋กœ ๋งŒ๋“ค๊ณ  ์žˆ์Œ
- ํ•œ ํ”„๋กœ์ ํŠธ์—์„œ ์ˆ˜์ •ํ•˜๋ฉด ๋‹ค๋ฅธ ๋‘ ๊ณณ์— ์ˆ˜๋™์œผ๋กœ ๋ณต์‚ฌํ•ด์•ผ ํ•จ

๋ชจ๋…ธ๋ ˆํฌ์˜ ํ•ด๊ฒฐ์ฑ…:

youngsu-monorepo/
โ”œโ”€โ”€ apps/
โ”‚   โ”œโ”€โ”€ community/      (Next.js ํ”„๋ก ํŠธ์—”๋“œ)
โ”‚   โ”œโ”€โ”€ admin/          (Next.js ์–ด๋“œ๋ฏผ)
โ”‚   โ””โ”€โ”€ api/            (NestJS ๋ฐฑ์—”๋“œ)
โ””โ”€โ”€ packages/
    โ”œโ”€โ”€ ui/             (๊ณตํ†ต UI ์ปดํฌ๋„ŒํŠธ)
    โ”œโ”€โ”€ types/          (๊ณตํ†ต ํƒ€์ž…)
    โ””โ”€โ”€ config/         (๊ณตํ†ต ESLint, TypeScript ์„ค์ •)

๐Ÿ—‚๏ธ pnpm-workspace.yaml ๊ธฐ๋ณธ ๊ตฌ์„ฑ

์ตœ์†Œ ์„ค์ •

# pnpm-workspace.yaml (๋ ˆํฌ ๋ฃจํŠธ ์˜์—ญ์— ์ƒ์„ฑ)
# ๐Ÿ’ก ์ด ํŒŒ์ผ ํ•˜๋‚˜๊ฐ€ "์—ฌ๊ธฐ์„œ๋ถ€ํ„ฐ ๋ชจ๋…ธ๋ ˆํฌ์•ผ!" ๋ผ๊ณ  ์„ ์–ธํ•˜๋Š” ํ˜ธ์ ๋“ฑ๋ณธ ๊ฐ™์€ ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.
packages:
  # ๐Ÿฆ apps ํด๋” ๋ฐ‘์— ์žˆ๋Š” ๋ชจ๋“  ํด๋”๋ฅผ ํ•˜๋‚˜์˜ ๊ฐœ๋ณ„ ํ”„๋กœ์ ํŠธ(Workspace ํŒจํ‚ค์ง€)๋กœ ์ทจ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค.
  # ์˜ˆ: apps/community, apps/admin ๋“ฑ
  - 'apps/*'
  
  # ๐Ÿฆ packages ํด๋” ๋ฐ‘์— ์žˆ๋Š” ๊ฒƒ๋“ค๋„ ๋™์ผํ•˜๊ฒŒ ๊ฐœ๋ณ„ ํŒจํ‚ค์ง€๋กœ ์ทจ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค.
  # ์˜ˆ: packages/ui, packages/types ๋“ฑ
  - 'packages/*'
  
  # ๋งŒ์•ฝ ํ•˜์œ„ ํด๋”๊ฐ€ ๋” ๊นŠ๊ฒŒ ์žˆ๋‹ค๋ฉด ** (๊ธ€๋กœ๋ธŒ) ํŒจํ„ด์„ ์“ธ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
  - 'packages/**'
  
  # ๐Ÿ’ก ๋А๋‚Œํ‘œ(!)๋ฅผ ๋ถ™์ด๋ฉด ํŠน์ • ํด๋”๋ฅผ ๋ชจ๋…ธ๋ ˆํฌ ๋Œ€์ƒ์—์„œ ์ œ์™ธ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  # ํ…Œ์ŠคํŠธ ํด๋”๋‚˜ ์บ์‹œ ํด๋”๋Š” ํŒจํ‚ค์ง€๊ฐ€ ์•„๋‹ˆ๋‹ˆ๊นŒ ์ œ์™ธํ•ด์ค๋‹ˆ๋‹ค.
  - '!**/test/**'
  - '!**/.cache/**'

๊ฐ ํŒจํ‚ค์ง€์˜ package.json

// packages/ui/package.json
{
  "name": "@youngsu/ui",
  "version": "1.0.0",
  "main": "./src/index.ts",
  "exports": {
    ".": "./src/index.ts"
  }
}
// packages/types/package.json
{
  "name": "@youngsu/types",
  "version": "1.0.0",
  "main": "./src/index.ts",
  "types": "./src/index.ts"
}
// apps/community/package.json
{
  "name": "@youngsu/community",
  "dependencies": {
    "@youngsu/ui": "workspace:*",
    "@youngsu/types": "workspace:*"
  }
}

๐Ÿ”— workspace ํŒจํ‚ค์ง€ ๊ฐ„ ์˜์กด์„ฑ โ€” workspace: ํ”„๋กœํ† ์ฝœ

workspace: ํ”„๋กœํ† ์ฝœ์ด๋ž€

๊ฐ™์€ workspace ์•ˆ์˜ ํŒจํ‚ค์ง€๋ฅผ ์˜์กด์„ฑ์œผ๋กœ ์—ฐ๊ฒฐํ•  ๋•Œ workspace: ํ”„๋กœํ† ์ฝœ์„ ์‚ฌ์šฉํ•œ๋‹ค. npm ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ์—์„œ ์ฐพ์ง€ ์•Š๊ณ  ๋กœ์ปฌ ํŒจํ‚ค์ง€๋ฅผ ์ง์ ‘ ์ฐธ์กฐ ํ•œ๋‹ค.

{
  "dependencies": {
    "@youngsu/ui": "workspace:*",       // ํ˜„์žฌ ๋กœ์ปฌ ๋ฒ„์ „ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ
    "@youngsu/types": "workspace:^",    // SemVer ^ ๋ฒ”์œ„๋กœ ๋กœ์ปฌ ์ฐธ์กฐ
    "@youngsu/config": "workspace:~"    // SemVer ~ ๋ฒ”์œ„๋กœ ๋กœ์ปฌ ์ฐธ์กฐ
  }
}
ํ‘œ๊ธฐ์˜๋ฏธ
workspace:*๋กœ์ปฌ ํŒจํ‚ค์ง€์˜ ํ˜„์žฌ ๋ฒ„์ „์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ (๊ฐ€์žฅ ๋งŽ์ด ์”€)
workspace:^ํ˜„์žฌ ๋ฒ„์ „์„ ^1.2.3 ํ˜•ํƒœ๋กœ ์ฐธ์กฐ
workspace:~ํ˜„์žฌ ๋ฒ„์ „์„ ~1.2.3 ํ˜•ํƒœ๋กœ ์ฐธ์กฐ
workspace:1.0.0์ •ํ™•ํ•œ ๋ฒ„์ „ ์ง€์ •

publish ํ•  ๋•Œ ๋ณ€ํ™˜

pnpm publish ๋˜๋Š” pnpm pack ์‹œ์— workspace:* ๊ฐ€ ์‹ค์ œ ๋ฒ„์ „ ๋ฒ”์œ„๋กœ ์ž๋™ ๋ณ€ํ™˜ ๋œ๋‹ค.

// ๋ฐฐํฌ ์ „ (๋กœ์ปฌ)
{ "@youngsu/ui": "workspace:*" }
 
// ๋ฐฐํฌ ํ›„ (์‹ค์ œ npm ํŒจํ‚ค์ง€)
{ "@youngsu/ui": "^1.0.0" }  // ํ˜„์žฌ @youngsu/ui ๋ฒ„์ „์ด 1.0.0 ์ด๋ฉด

workspace ํŒจํ‚ค์ง€ ์ถ”๊ฐ€

# @youngsu/community ์— @youngsu/ui ์˜์กด์„ฑ ์ถ”๊ฐ€
pnpm --filter @youngsu/community add @youngsu/ui
 
# pnpm ์ด ์ž๋™์œผ๋กœ workspace: ํ”„๋กœํ† ์ฝœ์„ ๊ฐ์ง€ํ•ด์„œ ๋กœ์ปฌ ์—ฐ๊ฒฐ

๐ŸŽฏ --filter ์…€๋ ‰ํ„ฐ ์™„์ „ ์ •๋ณต

๊ธฐ๋ณธ ํŒจํ„ด ๋งค์นญ

# ์ •ํ™•ํ•œ ํŒจํ‚ค์ง€๋ช…
pnpm --filter @youngsu/ui build
 
# ์Šค์ฝ”ํ”„ ์ „์ฒด
pnpm --filter "@youngsu/*" build
 
# ์ด๋ฆ„ ํŒจํ„ด (์™€์ผ๋“œ์นด๋“œ)
pnpm --filter "*community*" dev
 
# ๋‹จ์ถ•ํ˜• -F
pnpm -F @youngsu/ui build

์˜์กด์„ฑ ๊ธฐ์ค€ ์„ ํƒ

# @youngsu/ui ์™€ @youngsu/ui ๊ฐ€ ์˜์กดํ•˜๋Š” ํŒจํ‚ค์ง€๋“ค ๋ชจ๋‘
pnpm --filter "@youngsu/ui..." build
 
# @youngsu/ui ์— ์˜์กดํ•˜๋Š” ๋ชจ๋“  ํŒจํ‚ค์ง€๋“ค (community, admin ๋“ฑ)
pnpm --filter "...@youngsu/ui" build
 
# @youngsu/ui ์˜ ์ง์ ‘ ์˜์กด์„ฑ๋งŒ (ui ์ž์‹  ์ œ์™ธ)
pnpm --filter "@youngsu/ui^..." build

๋ณ€๊ฒฝ๋œ ํŒŒ์ผ ๊ธฐ์ค€ ์„ ํƒ (CI ์—์„œ ๊ฐ•๋ ฅ)

# main ๋ธŒ๋žœ์น˜ ๋Œ€๋น„ ๋ณ€๊ฒฝ๋œ ํŒจํ‚ค์ง€๋งŒ ๋นŒ๋“œ
pnpm --filter "[origin/main]" build
 
# main ๋Œ€๋น„ ๋ณ€๊ฒฝ๋œ ํŒจํ‚ค์ง€ + ๊ทธ์— ์˜์กดํ•˜๋Š” ํŒจํ‚ค์ง€ ๋ชจ๋‘
pnpm --filter "...[origin/main]" build
 
# apps/ ๋””๋ ‰ํ† ๋ฆฌ ๋‚ด ๋ณ€๊ฒฝ๋œ ํŒจํ‚ค์ง€๋งŒ
pnpm --filter "{apps/**}[origin/main]" build

๋””๋ ‰ํ† ๋ฆฌ ๊ธฐ์ค€ ์„ ํƒ

# apps/ ํ•˜์œ„ ๋ชจ๋“  ํŒจํ‚ค์ง€
pnpm --filter "./apps/**" dev
 
# ํ˜„์žฌ ๋””๋ ‰ํ† ๋ฆฌ ๊ธฐ์ค€ (cd apps/community ํ›„)
pnpm --filter "." dev

๐Ÿ”„ pnpm -r โ€” recursive ๋ช…๋ น์–ด

-r ๋˜๋Š” --recursive ๋Š” workspace ์˜ ๋ชจ๋“  ํŒจํ‚ค์ง€์— ๋ช…๋ น์„ ์‹คํ–‰ํ•œ๋‹ค.

# ๋ชจ๋“  ํŒจํ‚ค์ง€ ์„ค์น˜
pnpm -r install
 
# ๋ชจ๋“  ํŒจํ‚ค์ง€ ๋นŒ๋“œ (์˜์กด์„ฑ ์ˆœ์„œ๋Œ€๋กœ)
pnpm -r build
 
# ๋ชจ๋“  ํŒจํ‚ค์ง€ ํ…Œ์ŠคํŠธ
pnpm -r test
 
# ๋ชจ๋“  ํŒจํ‚ค์ง€ lint
pnpm -r lint
 
# ํŠน์ • script ๊ฐ€ ์žˆ๋Š” ํŒจํ‚ค์ง€์—๋งŒ ์‹คํ–‰
pnpm -r --if-present test

๋ณ‘๋ ฌ vs ์ˆœ์ฐจ ์‹คํ–‰

# ๊ธฐ๋ณธ: ์˜์กด์„ฑ ๊ทธ๋ž˜ํ”„ ์ˆœ์„œ๋กœ ์‹คํ–‰ (์ˆœ์ฐจ)
pnpm -r build
 
# ๊ฐ•์ œ ๋ณ‘๋ ฌ ์‹คํ–‰ (์˜์กด์„ฑ ๋ฌด์‹œ - ์ฃผ์˜)
pnpm -r --parallel build
 
# ๋™์‹œ ์‹คํ–‰ ์ˆ˜ ์ œํ•œ
pnpm -r --workspace-concurrency=3 build

๋ฃจํŠธ ํŒจํ‚ค์ง€ ํฌํ•จ

# ๋ฃจํŠธ package.json ์˜ ์Šคํฌ๋ฆฝํŠธ๋„ ํฌํ•จํ•ด์„œ ์‹คํ–‰
pnpm -r --include-workspace-root build

๐Ÿ—๏ธ ์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ ๋ชจ๋…ธ๋ ˆํฌ ์‹ค์ „ ๊ตฌ์„ฑ

์ตœ์ข… ๊ตฌ์กฐ

youngsu-monorepo/
โ”œโ”€โ”€ pnpm-workspace.yaml
โ”œโ”€โ”€ package.json                    (๋ฃจํŠธ โ€” ๊ณตํ†ต devDeps, scripts)
โ”œโ”€โ”€ .npmrc
โ”œโ”€โ”€ apps/
โ”‚   โ”œโ”€โ”€ community/                  (@youngsu/community)
โ”‚   โ”‚   โ”œโ”€โ”€ package.json
โ”‚   โ”‚   โ””โ”€โ”€ src/
โ”‚   โ””โ”€โ”€ admin/                      (@youngsu/admin)
โ”‚       โ”œโ”€โ”€ package.json
โ”‚       โ””โ”€โ”€ src/
โ””โ”€โ”€ packages/
    โ”œโ”€โ”€ ui/                         (@youngsu/ui)
    โ”‚   โ”œโ”€โ”€ package.json
    โ”‚   โ””โ”€โ”€ src/
    โ”œโ”€โ”€ types/                      (@youngsu/types)
    โ”‚   โ”œโ”€โ”€ package.json
    โ”‚   โ””โ”€โ”€ src/
    โ””โ”€โ”€ config/                     (@youngsu/config)
        โ”œโ”€โ”€ eslint.js
        โ””โ”€โ”€ tsconfig.base.json

pnpm-workspace.yaml

# pnpm-workspace.yaml
packages:
  - 'apps/*'
  - 'packages/*'

๋ฃจํŠธ package.json

// package.json (๋ชจ๋…ธ๋ ˆํฌ ๋ฃจํŠธ - ์ตœ์ƒ๋‹จ)
// ๐Ÿ’ก ๋ฃจํŠธ ํŒจํ‚ค์ง€๋Š” ์ž์ฒด์ ์œผ๋กœ ๋ฐฐํฌํ•  ์•ฑ์ด ์•„๋‹ˆ๋ผ, ๋ชจ๋…ธ๋ ˆํฌ ์ „์ฒด๋ฅผ ๋ฌถ์–ด์ฃผ๋Š” ๊ป๋ฐ๊ธฐ ์—ญํ• ์ž…๋‹ˆ๋‹ค.
{
  "name": "youngsu-monorepo",
  "private": true, // ๐Ÿฆ [์ค‘์š”] ๋ฃจํŠธ ์ž์ฒด๋ฅผ npm์— ์ž˜๋ชป ๋ฐฐํฌํ•˜๋Š” ์‹ค์ˆ˜๋ฅผ ๋ง‰์•„์ค๋‹ˆ๋‹ค.
  "packageManager": "pnpm@9.15.0",
  "engines": {
    "node": ">=20.0.0",
    "pnpm": ">=9.0.0"
  },
  "scripts": {
    // ๐Ÿฆ [ํ•ต์‹ฌ] '-r' ์˜ต์…˜์€ 'recursive(์žฌ๊ท€)'์˜ ์•ฝ์ž๋กœ, ํ•˜์œ„์˜ ๋ชจ๋“  ํŒจํ‚ค์ง€๋ฅผ ๋Œ๋ฉด์„œ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜๋ผ๋Š” ๋œป์ž…๋‹ˆ๋‹ค.
    
    // ๐Ÿ’ก ์ „์ฒด ๋นŒ๋“œ: main ๋ธŒ๋žœ์น˜ ๊ธฐ์ค€์œผ๋กœ "๋ณ€๊ฒฝ๋œ ์†Œ์Šค"๊ฐ€ ์žˆ๋Š” ํŒจํ‚ค์ง€์™€ ๊ทธ์— ์˜ํ–ฅ์„ ๋ฐ›๋Š” ํŒจํ‚ค์ง€๋งŒ ์ถ”๋ ค์„œ ๋นŒ๋“œํ•ฉ๋‹ˆ๋‹ค. (์บ์‹ฑ ๊ทน๋Œ€ํ™”)
    "build": "pnpm -r --filter \"[origin/main]...\" build",
    
    // ๐Ÿ’ก ์ „์ฒด ๊ฐœ๋ฐœ๋ชจ๋“œ ์ผœ๊ธฐ: --parallel ์˜ต์…˜์„ ์ฃผ๋ฉด admin๊ณผ community ์•ฑ ๋ชจ๋‘ ๋™์‹œ์— ์‹คํ–‰์ฐฝ์ด ๋œน๋‹ˆ๋‹ค.
    "dev": "pnpm -r --parallel --filter \"./apps/**\" dev",
    
    // ํƒ€์ž… ๊ฒ€์‚ฌ: ๋ชจ๋“  ํŒจํ‚ค์ง€์— ๋Œ€ํ•ด ์ˆœ์„œ๋Œ€๋กœ ์ง„ํ–‰
    "typecheck": "pnpm -r typecheck",
    
    // ํ…Œ์ŠคํŠธ: ๋ณ€๊ฒฝ๋œ ํŒจํ‚ค์ง€๋งŒ ๋˜‘๋˜‘ํ•˜๊ฒŒ ๊ณจ๋ผ์„œ ํ…Œ์ŠคํŠธ
    "test": "pnpm --filter \"...[origin/main]\" test",
    
    // ๐Ÿ’ก --if-present ์˜ต์…˜: ํ•˜์œ„ ํŒจํ‚ค์ง€ ์ค‘์— "lint" ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์ ํ˜€์žˆ๋Š” ๊ณณ์—์„œ๋งŒ ์•Œ์•„์„œ ์‹คํ–‰ํ•˜๊ณ , ์—†์œผ๋ฉด ๋ฌด์‹œํ•ฉ๋‹ˆ๋‹ค.
    "lint": "pnpm -r --if-present lint"
  },
  "devDependencies": {
    // ๐Ÿฆ ์ „์ฒด ์•ฑ์—์„œ ๊ณตํ†ต์œผ๋กœ ์“ฐ๋Š” ๋„๊ตฌ(ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ, ESLint ๋“ฑ)๋Š” ๋ฃจํŠธ์— ํ•œ ๋ฒˆ๋งŒ ๊น๋‹ˆ๋‹ค.
    // ๊ฐ ์•ฑ๋งˆ๋‹ค ๋ฐ˜๋ณตํ•ด์„œ ๊น” ํ•„์š”๊ฐ€ ์—†์ด pnpm์ด ์•Œ์•„์„œ ๋‹ค ์—ฐ๊ฒฐํ•ด์ค๋‹ˆ๋‹ค.
    "typescript": "^5.3.3",
    "@types/node": "^20.11.0",
    "eslint": "^8.57.0",
    "prettier": "^3.2.5"
  }
}

์•ฑ package.json (apps/community)

// apps/community/package.json
{
  "name": "@youngsu/community",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "typecheck": "tsc --noEmit",
    "lint": "next lint"
  },
  "dependencies": {
    "next": "^14.2.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    
    // ๐Ÿฆ [์ดˆํ•ต์‹ฌ] ๋ชจ๋…ธ๋ ˆํฌ ๋‚ด๋ถ€ ํŒจํ‚ค์ง€ ์—ฐ๊ฒฐ์˜ ๋งˆ๋ฒ•!
    // NPM ์›๊ฒฉ ์ €์žฅ์†Œ๊ฐ€ ์•„๋‹ˆ๋ผ, ์šฐ๋ฆฌ ๋กœ์ปฌ ํด๋”(packages/ui)์— ์žˆ๋Š” ์ฝ”๋“œ๋ฅผ ๋ฐ”๋กœ ๊ฐ€์ ธ๋‹ค ์”๋‹ˆ๋‹ค.
    // "workspace:*" ๋Š” ํ•ญ์ƒ ๋กœ์ปฌ์˜ ์ตœ์‹  ์ฝ”๋“œ๋ฅผ ๋ฐ”๋ผ๋ณด๊ฒ ๋‹ค๋Š” ๋œป์ž…๋‹ˆ๋‹ค. (์‹ฌ๋งํฌ ํšจ๊ณผ)
    "@youngsu/ui": "workspace:*",
    "@youngsu/types": "workspace:*"
  },
  "devDependencies": {
    // ๊ณตํ†ต ์„ค์ •ํŒŒ์ผ(packages/config)๋„ ๋™์ผํ•˜๊ฒŒ workspace ํ”„๋กœํ† ์ฝœ๋กœ ๋‹น๊ฒจ์˜ต๋‹ˆ๋‹ค.
    "@youngsu/config": "workspace:*"
  }
}

๊ณตํ†ต UI ํŒจํ‚ค์ง€ (packages/ui)

// packages/ui/package.json
{
  "name": "@youngsu/ui",
  "version": "1.0.0",
  "exports": {
    ".": {
      "import": "./src/index.ts",
      "types": "./src/index.ts"
    },
    "./button": "./src/components/Button.tsx",
    "./card": "./src/components/Card.tsx"
  },
  "scripts": {
    "typecheck": "tsc --noEmit",
    "lint": "eslint src/"
  },
  "devDependencies": {
    "react": "^18.2.0",
    "@types/react": "^18.2.0"
  },
  "peerDependencies": {
    "react": "^18.2.0"
  }
}

๊ณตํ†ต TypeScript ์„ค์ • (packages/config)

// packages/config/tsconfig.base.json
{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "baseUrl": "."
  }
}
// apps/community/tsconfig.json โ€” ๊ณตํ†ต ์„ค์ • ํ™•์žฅ
{
  "extends": "@youngsu/config/tsconfig.base.json",
  "compilerOptions": {
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
  "exclude": ["node_modules"]
}

์‹ค์ „ ์›Œํฌํ”Œ๋กœ์šฐ

# ์ „์ฒด ์„ค์น˜
pnpm install
 
# community ์•ฑ๋งŒ ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹คํ–‰
pnpm --filter @youngsu/community dev
 
# UI ํŒจํ‚ค์ง€ ๋ณ€๊ฒฝ ํ›„ ํƒ€์ž… ์ฒดํฌ
pnpm --filter "@youngsu/ui..." typecheck
 
# community ์— ์ƒˆ ํŒจํ‚ค์ง€ ์ถ”๊ฐ€
pnpm --filter @youngsu/community add axios
 
# ๋ฃจํŠธ devDep ์ถ”๊ฐ€
pnpm add -D -w eslint-plugin-tailwindcss
# -w: --workspace-root (๋ฃจํŠธ package.json ์— ์ถ”๊ฐ€)
 
# ๋ณ€๊ฒฝ๋œ ํŒจํ‚ค์ง€์™€ ์˜ํ–ฅ๋ฐ›๋Š” ์•ฑ๋งŒ ๋นŒ๋“œ (CI ์ตœ์ ํ™”)
pnpm --filter "...[origin/main]" build

๐Ÿ ์ด์ •๋ฆฌ

pnpm workspace ํ•ต์‹ฌ ํŒจํ„ด
โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
๊ตฌ์„ฑ:     pnpm-workspace.yaml ์— packages: ['apps/*', 'packages/*']
๋‚ด๋ถ€ ์ฐธ์กฐ: "workspace:*" ํ”„๋กœํ† ์ฝœ๋กœ ๋กœ์ปฌ ํŒจํ‚ค์ง€ ์—ฐ๊ฒฐ
ํŠน์ • ํŒจํ‚ค์ง€: --filter @scope/name ์œผ๋กœ ํƒ€๊ฒŸ ์ง€์ •
์˜์กด ์ถ”์ :  --filter "...@scope/name" ๋กœ ์˜ํ–ฅ๋ฐ›๋Š” ํŒจํ‚ค์ง€ ์„ ํƒ
๋ณ€๊ฒฝ ๊ฐ์ง€:  --filter "[origin/main]" ๋กœ CI ์ตœ์ ํ™”
์ „์ฒด ์‹คํ–‰:  pnpm -r ๋กœ recursive ์‹คํ–‰
๋ฃจํŠธ ์„ค์น˜:  pnpm add -D -w (--workspace-root) ํ”Œ๋ž˜๊ทธ
โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”

๐Ÿ“ ๋งˆ๋ฌด๋ฆฌ ํ€ด์ฆˆ

Q1. workspace:* ์™€ workspace:^ ์˜ ์ฐจ์ด๋Š” ๋ฌด์—‡์ด๋ฉฐ, ์–ธ์ œ ๊ฐ๊ฐ์„ ์‚ฌ์šฉํ•˜๋Š”๊ฐ€?

โœ… ์ •๋‹ต: workspace:* ๋Š” ํ˜„์žฌ ๋กœ์ปฌ ๋ฒ„์ „์„ ๊ทธ๋Œ€๋กœ ์ฐธ์กฐํ•˜๊ณ , ๋ฐฐํฌ ์‹œ "*" ํ˜•ํƒœ๋กœ ๋ณ€ํ™˜๋œ๋‹ค. workspace:^ ๋Š” ๋ฐฐํฌ ์‹œ "^1.2.3" ์ฒ˜๋Ÿผ SemVer ๋ฒ”์œ„ ๋กœ ๋ณ€ํ™˜๋œ๋‹ค. ๋‚ด๋ถ€ ์•ฑ(private: true)์—์„œ๋Š” ๋ฐฐํฌํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ workspace:* ๊ฐ€ ์ผ๋ฐ˜์ ์ด๊ณ , ์™ธ๋ถ€์— publish ํ•˜๋Š” ํŒจํ‚ค์ง€์—์„œ ๋‹ค๋ฅธ workspace ํŒจํ‚ค์ง€๋ฅผ ์ฐธ์กฐํ•  ๋•Œ๋Š” workspace:^ ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:

  • workspace:* โ†’ publish ํ›„ "@youngsu/ui": "*" (์–ด๋–ค ๋ฒ„์ „์ด๋“  OK) โ€” ๋‚ด๋ถ€ ์•ฑ์— ์ ํ•ฉ
  • workspace:^ โ†’ publish ํ›„ "@youngsu/ui": "^1.0.0" (semver ๋ฒ”์œ„) โ€” ์™ธ๋ถ€ ๋ฐฐํฌ ํŒจํ‚ค์ง€์— ์ ํ•ฉ
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: "๋‚ด๋ถ€์šฉ ์•ฑ์€ *, ์™ธ๋ถ€ ๋ฐฐํฌํ•˜๋Š” ํŒจํ‚ค์ง€๋Š” ^"

Q2. pnpm --filter "...[origin/main]" build ๋ช…๋ น์–ด๊ฐ€ CI ์—์„œ ์™œ ์œ ์šฉํ•œ๊ฐ€?

โœ… ์ •๋‹ต: [origin/main] ๋Š” main ๋ธŒ๋žœ์น˜ ๋Œ€๋น„ ๋ณ€๊ฒฝ๋œ ํŒจํ‚ค์ง€ ๋ฅผ ์„ ํƒํ•˜๊ณ , ์•ž์˜ ... (ellipsis) ๋Š” ๊ทธ ํŒจํ‚ค์ง€์— ์˜์กดํ•˜๋Š” ๋ชจ๋“  ํŒจํ‚ค์ง€ ๊นŒ์ง€ ํฌํ•จํ•œ๋‹ค. ์ฆ‰, @youngsu/ui ๊ฐ€ ๋ณ€๊ฒฝ๋๋‹ค๋ฉด @youngsu/ui ์— ์˜์กดํ•˜๋Š” @youngsu/community, @youngsu/admin ๋„ ์ž๋™์œผ๋กœ ํฌํ•จ๋˜์–ด ๋นŒ๋“œ๋œ๋‹ค. ๋ณ€๊ฒฝ์ด ์—†๋Š” ํŒจํ‚ค์ง€๋Š” ๋นŒ๋“œํ•˜์ง€ ์•Š์•„ CI ์‹œ๊ฐ„์„ ํฌ๊ฒŒ ์ค„์ผ ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:

  • ๋ชจ๋…ธ๋ ˆํฌ ๊ทœ๋ชจ๊ฐ€ ์ปค์ง€๋ฉด ์ „์ฒด ๋นŒ๋“œ์— ์ˆ˜์‹ญ ๋ถ„์ด ๊ฑธ๋ฆด ์ˆ˜ ์žˆ์Œ
  • ๋ณ€๊ฒฝ๋œ ํŒจํ‚ค์ง€ + ์˜ํ–ฅ๋ฐ›๋Š” ํŒจํ‚ค์ง€๋งŒ ๋นŒ๋“œ โ†’ PR ๋‹น CI ์‹œ๊ฐ„ 80% ์ด์ƒ ๋‹จ์ถ• ๊ฐ€๋Šฅ
  • Turborepo ์˜ ์บ์‹ฑ๊ณผ ๋ณ‘ํ–‰ํ•˜๋ฉด ๋”์šฑ ๊ฐ•๋ ฅํ•ด์ง
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: "๋ณ€๊ฒฝ๋œ ๊ฒƒ + ๊ทธ๊ฑธ ์“ฐ๋Š” ๊ฒƒ๋งŒ ๋นŒ๋“œ โ†’ ...[origin/main]"

Q3. ์˜์ฒ ์ด์˜ ํ…Œ์ŠคํŠธ ํƒ€์ž„: ์•„ํ‚คํ…์ฒ˜ ์„ค๊ณ„ (์˜์ˆ™์˜ UX ์ž”์†Œ๋ฆฌ)

์˜์ˆ™ ๋‹˜์ด ๋””์ž์ธ ๋ฆฌ๋ทฐ์—์„œ ๋งํ–ˆ๋‹ค.
"์˜์ฒ  ๋‹˜, apps/community ์™€ apps/admin ์˜ ๋ฒ„ํŠผ ์Šคํƒ€์ผ์ด ์„œ๋กœ ๋‹ฌ๋ผ์š”. ์‚ฌ์šฉ์ž๊ฐ€ ์ปค๋ฎค๋‹ˆํ‹ฐ์—์„œ ์–ด๋“œ๋ฏผ์œผ๋กœ ์ด๋™ํ•˜๋ฉด UI ๊ฐ€ ๋‹ค๋ฅด๊ฒŒ ๋А๊ปด์ ธ์„œ ํ˜ผ๋ž€์Šค๋Ÿฝ๋Œ€์š”."

์˜์ฒ ์ด๊ฐ€ packages/ui ์— ๊ณตํ†ต Button ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  ๋‘ ์•ฑ์—์„œ ์“ฐ๋ ค๊ณ  ํ•œ๋‹ค. ์ด๋•Œ pnpm workspace ๋ฅผ ํ™œ์šฉํ•œ ์˜ฌ๋ฐ”๋ฅธ ๊ตฌ์„ฑ ๋ฐฉ๋ฒ•์€?

โœ… ์ •๋‹ต: packages/ui/src/components/Button.tsx ๋ฅผ ๋งŒ๋“ค๊ณ , packages/ui/package.json ์˜ exports ์— "./button": "./src/components/Button.tsx" ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค. ๊ทธ ๋‹ค์Œ apps/community ์™€ apps/admin ์˜ package.json ์— "@youngsu/ui": "workspace:*" ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  pnpm install ํ•˜๋ฉด ๋œ๋‹ค.

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:

  • ๊ตฌ์„ฑ ์ˆœ์„œ:
    1. packages/ui ์— ์ปดํฌ๋„ŒํŠธ ์ž‘์„ฑ
    2. packages/ui/package.json exports ํ•„๋“œ ๋“ฑ๋ก
    3. ๊ฐ ์•ฑ์—์„œ pnpm --filter @youngsu/community add @youngsu/ui ์‹คํ–‰
    4. import { Button } from '@youngsu/ui/button' ๋˜๋Š” from '@youngsu/ui' ๋กœ ์‚ฌ์šฉ
  • TypeScript ์„ค์ • ์ฃผ์˜: apps/community/tsconfig.json ์˜ paths ์— @youngsu/ui ๊ฒฝ๋กœ ๋งคํ•‘์ด ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Œ
  • HMR ์ฃผ์˜: Next.js ์—์„œ packages/ui ํŒŒ์ผ ์ˆ˜์ • ์‹œ next.config.js ์— transpilePackages: ['@youngsu/ui'] ์ถ”๊ฐ€ ํ•„์š”
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: "workspace ํŒจํ‚ค์ง€ ์ถ”๊ฐ€ โ†’ workspace:* ์„ ์–ธ โ†’ pnpm install โ†’ import."

๐Ÿฃ ์˜์ฒ ์ด์˜ ํ‡ด๊ทผ ์ผ๊ธฐ

์˜ค๋Š˜ ๋“œ๋””์–ด ๋ชจ๋…ธ๋ ˆํฌ ์ดˆ๊ธฐ ์„ค์ •์„ ๋งˆ์ณค๋‹ค. ์ฒ˜์Œ์—” ๋„ˆ๋ฌด ๋ณต์žกํ•ด ๋ณด์—ฌ์„œ "์ด๊ฑฐ ๋‹ค ์„ค์ •ํ•˜๋‹ค๊ฐ€ ํ•˜๋ฃจ ๋‹ค ๋‚ ๋ฆฌ๊ฒ ๋‹ค"๊ณ  ์ƒ๊ฐํ–ˆ๋Š”๋ฐ, ์ƒ๊ฐ๋ณด๋‹ค ํ›จ์”ฌ ๊ฐ„๋‹จํ–ˆ๋‹ค.

pnpm-workspace.yaml ์— ํด๋” ๊ฒฝ๋กœ ๋‘ ์ค„, ๊ฐ ํŒจํ‚ค์ง€์— workspace:* ์—ฐ๊ฒฐ, pnpm install ํ•œ ๋ฒˆ โ€” ์ด๊ฒŒ ์ „๋ถ€์˜€๋‹ค. ๋ฌผ๋ก  TypeScript paths ์„ค์ •์ด๋‚˜ Next.js transpilePackages ๊ฐ™์€ ์„ธ๋ถ€ ์„ค์ •์€ ๋” ์žˆ์—ˆ์ง€๋งŒ, ํ•ต์‹ฌ ๊ฐœ๋…์€ ์ •๋ง ๋‹จ์ˆœํ•˜๋‹ค.

--filter "...[origin/main]" ์ด ๊ฐ€์žฅ ์ธ์ƒ์ ์ด์—ˆ๋‹ค. CI ์—์„œ ๋ณ€๊ฒฝ๋œ ํŒจํ‚ค์ง€์™€ ๊ทธ์— ์˜์กดํ•˜๋Š” ํŒจํ‚ค์ง€๋งŒ ๋นŒ๋“œํ•œ๋‹ค๋Š” ๊ฐœ๋…์ด ๋„ˆ๋ฌด ์šฐ์•„ํ•˜๋‹ค. ์ „์ฒด๋ฅผ ๋งค๋ฒˆ ๋นŒ๋“œํ•˜๋˜ ๊ฒƒ๊ณผ ๋น„๊ตํ•˜๋ฉด CI ์‹œ๊ฐ„์ด ๋ช‡ ๋ฐฐ๋Š” ์ค„์–ด๋“ค๊ฒ ๋‹ค.

๐Ÿ’ก ์˜ค๋Š˜์˜ ๊ตํ›ˆ: "๋ชจ๋…ธ๋ ˆํฌ๊ฐ€ ๋ณต์žกํ•ด ๋ณด์ด๋Š” ๊ฑด ๋„๊ตฌ๋ฅผ ๋ชจ๋ฅด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. workspace:* ์™€ --filter ๋‘ ๊ฐ€์ง€๋งŒ ์ดํ•ดํ•˜๋ฉด pnpm ๋ชจ๋…ธ๋ ˆํฌ์˜ ์ ˆ๋ฐ˜์€ ๋งˆ์Šคํ„ฐํ•œ ๊ฒƒ์ด๋‹ค."

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


๐Ÿ”— ๋” ์•Œ์•„๋ณด๊ธฐ