Skip to content

YSJ0228/Moving_fe

Repository files navigation

Docthru ๋ฉ”์ธ ํŽ˜์ด์ง€

ํˆฌ๋ช…ํ•˜๊ณ  ํ•ฉ๋ฆฌ์ ์ธ ์ด์‚ฌ! ๋ฌด๋น™ ๋ณด๋Ÿฌ๊ฐ€๊ธฐ -> ๋ฌด๋น™

Back-end ๊นƒํ—ˆ๋ธŒ Back-end

๐Ÿ“œ ๋ชฉ์ฐจ

  1. ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ
  2. ๊ธฐ๋Šฅ ๊ตฌํ˜„ ์˜์ƒ
  3. ์‹œ์Šคํ…œ ์•„ํ‚คํ…์ฒ˜
  4. ๊ธฐ์ˆ  ์Šคํƒ
  5. ์ฃผ์š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
  6. ํŒ€ ์†Œ๊ฐœ ๋ฐ ๋ฌธ์„œ
  7. ๊ฐœ์ธ๋ณ„ ์ฃผ์š” ์ž‘์—… ๋‚ด์—ญ
  8. ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ
  9. ์ฃผ์š” ๊ธฐ๋Šฅ ์ƒ์„ธ
  10. ์„ฑ๋Šฅ ์ตœ์ ํ™” ์ „๋žต
  11. ํŠธ๋Ÿฌ๋ธ” ์ŠˆํŒ…

๐Ÿ“ ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ

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

๐Ÿ’ป ๊ธฐ๋Šฅ ๊ตฌํ˜„ ์˜์ƒ

ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ ์˜์ƒ ์œ ํŠœ๋ธŒ ์ธ๋„ค์ผ


๐Ÿšง ์‹œ์Šคํ…œ ์•„ํ‚คํ…์ฒ˜

์‹œ์Šคํ…œ ์•„ํ‚คํ…์ฒ˜

โš™๏ธ ๊ธฐ์ˆ  ์Šคํƒ

โœ… Language

TypeScript

โœ… Framework & Libraries

Next.js Express TanStack Query TailwindCSS

โœ… Hosting & Deployment

Vercel AWS EC2 Route 53 Amazon CloudFront

โœ… Storage & Database

Amazon S3 Amazon RDS

โœ… CI/CD

GitHub Actions

โœ… Version Control

Git GitHub


๐Ÿ“š ์ฃผ์š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

๋ฐ์ดํ„ฐ ํŒจ์นญ / ์บ์‹ฑ
  • @tanstack/react-query: ์„œ๋ฒ„ ์ƒํƒœ ๊ด€๋ฆฌ์™€ ์บ์‹ฑ
  • @tanstack/react-query-devtools: React Query ๋””๋ฒ„๊น… ๋„๊ตฌ
์ƒํƒœ ๊ด€๋ฆฌ
  • zustand: ๊ฒฝ๋Ÿ‰ ํด๋ผ์ด์–ธํŠธ ์ƒํƒœ ๊ด€๋ฆฌ
๊ตญ์ œํ™”(i18n)
  • next-intl: ์„œ๋ฒ„/ํด๋ผ์ด์–ธํŠธ ํ†ตํ•ฉ ๊ตญ์ œํ™”
์ธ์ฆ / ๋ณด์•ˆ
  • jose: JWT/JWE/JWS ๋“ฑ JOSE ์ŠคํŽ™ ๊ตฌํ˜„
ํผ
  • react-hook-form: ํผํฌ๋จผ์Šค ์ค‘์‹ฌ์˜ ํผ ์ƒํƒœ ๊ด€๋ฆฌ
UI / UX ๋ณด์กฐ
  • react-toastify: ํ† ์ŠคํŠธ ์•Œ๋ฆผ
  • react-icons: ์•„์ด์ฝ˜ ์„ธํŠธ
  • react-intersection-observer: ์ธํ„ฐ์„น์…˜ ์˜ต์ €๋ฒ„ ํ›…(๋ฌดํ•œ ์Šคํฌ๋กค ๋“ฑ)
  • react-simple-star-rating: ๋ณ„์  ์ปดํฌ๋„ŒํŠธ
์ฝ˜ํ…์ธ  ํŒŒ์‹ฑ / ๋ณด์•ˆ
  • dompurify: XSS ๋ฐฉ์ง€์šฉ HTML ์ •ํ™”
  • html-react-parser: HTML ๋ฌธ์ž์—ด โ†’ React ๋…ธ๋“œ ํŒŒ์‹ฑ
๋‚ ์งœ / ์œ ํ‹ธ
  • date-fns: ๋‚ ์งœ ์œ ํ‹ธ๋ฆฌํ‹ฐ
  • lodash.throttle: ์Šค๋กœํ‹€๋ง ์œ ํ‹ธ
  • nanoid: ๊ณ ์œ  ID ์ƒ์„ฑ
  • clsx: ์กฐ๊ฑด๋ถ€ ํด๋ž˜์Šค ๋ฌธ์ž์—ด ์ƒ์„ฑ
๋„คํŠธ์›Œํ‚น / ์‹ค์‹œ๊ฐ„
  • event-source-polyfill: SSE ํด๋ฆฌํ•„
๋ชจ๋‹ˆํ„ฐ๋ง
  • @sentry/nextjs: Sentry ํ†ตํ•ฉ(์„œ๋ฒ„/ํด๋ผ์ด์–ธํŠธ ์—๋Ÿฌ ์ถ”์ )
๊ฐœ๋ฐœ ๋„๊ตฌ
  • eslint, eslint-config-next, @eslint/eslintrc: ๋ฆฐํŒ… ๊ตฌ์„ฑ
  • prettier, prettier-plugin-tailwindcss: ์ฝ”๋“œ/ํด๋ž˜์Šค ์ •๋ ฌ ํฌ๋งคํŒ…
  • tailwindcss, @tailwindcss/postcss: ์Šคํƒ€์ผ๋ง ๊ตฌ์„ฑ
  • typescript, @types/node, @types/react, @types/react-dom ๋“ฑ: ํƒ€์ž… ์‹œ์Šคํ…œ
  • jest, @testing-library/react, @testing-library/jest-dom, @testing-library/user-event: ๋‹จ์œ„/์ปดํฌ๋„ŒํŠธ ํ…Œ์ŠคํŠธ
  • @next/bundle-analyzer: ๋ฒˆ๋“ค ์‚ฌ์ด์ฆˆ ๋ถ„์„
  • @svgr/webpack: SVG โ†’ React ์ปดํฌ๋„ŒํŠธ ๋ณ€ํ™˜
  • concurrently: ๋ฉ€ํ‹ฐ ์Šคํฌ๋ฆฝํŠธ ๋ณ‘๋ ฌ ์‹คํ–‰

๐Ÿ‘ฅ ํŒ€ ์†Œ๊ฐœ ๋ฐ ๋ฌธ์„œ

ํŒ€์› ์†Œ๊ฐœ

์ด๋ฆ„ ์—ญํ•  GitHub ๊ฐœ์ธ ๊ฐœ๋ฐœ ๋ณด๊ณ ์„œ
๊น€์žฌ์šฑ ๐Ÿ‰ ํŒ€์žฅ @WooGie911 ๋ณด๊ณ ์„œ
๊น€์Šน์ค€ ๐Ÿ’ BE ๋‹ด๋‹น์ž @y10b ๋ณด๊ณ ์„œ
๋ฐฑ์ง€์—ฐ ๐Ÿ‘ ๋ฐœํ‘œ ๋‹ด๋‹น์ž @jyeon03 ๋ณด๊ณ ์„œ
๊น€์ˆ˜๋นˆ ๐Ÿ‡ ๋ถ€ํŒ€์žฅ @subinkim9755 ๋ณด๊ณ ์„œ
์œค์„ธ์ค€ ๐ŸŽ ๋ฌธ์„œ ๋‹ด๋‹น์ž @YSJ0228 ๋ณด๊ณ ์„œ
๋ฐ•๋ฏผ๊ทœ ๐Ÿˆ FE ๋‹ด๋‹น์ž @gksktl111 ๋ณด๊ณ ์„œ

ํŒ€ ๋ฌธ์„œ


๐Ÿ“‹ ๊ฐœ์ธ๋ณ„ ์ฃผ์š” ์ž‘์—… ๋‚ด์—ญ

๐Ÿ‰ ๊น€์žฌ์šฑ

frontend

  • ๋ฆฌ๋ทฐ ํŽ˜์ด์ง€

    • ์ž‘์„ฑ ๊ฐ€๋Šฅํ•œ ๋ฆฌ๋ทฐ ํŽ˜์ด์ง€ + ๋ฆฌ๋ทฐ ์ž‘์„ฑ ๋ชจ๋‹ฌ
    • ๋‚ด๊ฐ€ ์ž‘์„ฑํ•œ ๋ฆฌ๋ทฐ ๋ชฉ๋ก ํŽ˜์ด์ง€
    • React Query ๊ธฐ๋ฐ˜ ์ƒํƒœ ๊ด€๋ฆฌ
    • ๋ณ„์  ์„ ํƒ + ํ…์ŠคํŠธ ์ž…๋ ฅ ๊ฒ€์ฆ
  • ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ ์‹œ์Šคํ…œ

    • event-source-polyfill ๊ธฐ๋ฐ˜ SSE ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ
    • Zustand ์•Œ๋ฆผ ๋ฐ์ดํ„ฐ ์ƒํƒœ ๊ด€๋ฆฌ
    • React Query ์•Œ๋ฆผ ๋ชฉ๋ก ์ƒํƒœ ๊ด€๋ฆฌ
    • ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ ์ˆ˜์‹  + ํ‘œ์‹œ
  • ๊ณตํ†ต ์ปดํฌ๋„ŒํŠธ

    • Input ๊ณตํ†ต ์ปดํฌ๋„ŒํŠธ ์ œ์ž‘
    • ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ UI ์ปดํฌ๋„ŒํŠธ ์„ค๊ณ„

backend

  • ๋ฆฌ๋ทฐ ์‹œ์Šคํ…œ

    • Prisma ๋ฏธ๋“ค์›จ์–ด๋กœ ๊ฒฌ์  ํ™•์ • ์‹œ ๋นˆ ๋ฆฌ๋ทฐ ์ž๋™ ์ƒ์„ฑ
    • ์ด์‚ฌ ์™„๋ฃŒ ํ›„ ๋ฆฌ๋ทฐ ์ž‘์„ฑ ๊ฐ€๋Šฅ (๋นˆ ๋ฆฌ๋ทฐ PATCH)
    • 1:1 ๊ด€๊ณ„ ๋ณด์žฅ (ํ•˜๋‚˜์˜ ํ™•์ • ๊ฒฌ์ ๋‹น ํ•˜๋‚˜์˜ ๋ฆฌ๋ทฐ)
  • ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ ์‹œ์Šคํ…œ

    • ์•ก์…˜ ํ…Œ์ด๋ธ” ๊ธฐ๋ฐ˜ ๋กœ๊ทธ/์•Œ๋ฆผ ๊ด€๋ฆฌ (ํ™•์žฅ์„ฑ ๊ณ ๋ ค)
    • Prisma ๋ฏธ๋“ค์›จ์–ด๋กœ ์•ก์…˜ ์ƒ์„ฑ ๊ฐ์ง€ + ์•Œ๋ฆผ ์ž๋™ ์ƒ์„ฑ
    • SSE ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ ์ „์†ก
๐Ÿ’ ๊น€์Šน์ค€

frontend

  • ๊ฒฌ์ ์š”์ฒญ ํŽ˜์ด์ง€

    • ํ™œ์„ฑ ๊ฒฌ์  ์ƒํƒœ์— ๋”ฐ๋ฅธ ์Šค๋งˆํŠธ ๋ผ์šฐํŒ… (์ƒ์„ฑ/์ˆ˜์ • ์ž๋™ ๋ถ„๊ธฐ)
    • ์ฑ„ํŒ…ํ˜• UI: 4๋‹จ๊ณ„ ํ”„๋กœ์„ธ์Šค (์ด์‚ฌ์ข…๋ฅ˜ โ†’ ๋‚ ์งœ โ†’ ์ถœ๋ฐœ์ง€ โ†’ ๋„์ฐฉ์ง€)
    • date-fns ๊ธฐ๋ฐ˜ ์ปค์Šคํ…€ ๋‹ฌ๋ ฅ (ํƒ€์ž„์กด ์ด์Šˆ ํ•ด๊ฒฐ, UTC ๊ธฐ์ค€)
    • ์นด์นด์˜ค ์ฃผ์†Œ ๊ฒ€์ƒ‰ + ์—๋Ÿฌ ๋ณต๊ตฌ ๋กœ์ง
    • ํผ ์ดํƒˆ ๋ฐฉ์ง€ ์‹œ์Šคํ…œ (useUnsavedChangesGuard)
    • React Query ๊ธฐ๋ฐ˜ ์ƒํƒœ ๊ด€๋ฆฌ + Dirty Check
  • ์ฐœํ•œ ๊ธฐ์‚ฌ๋‹˜ ํŽ˜์ด์ง€

    • ๋ฌดํ•œ์Šคํฌ๋กค (React Query Infinite Query + Intersection Observer)
    • ๋Œ€๋Ÿ‰ ์„ ํƒ/์‚ญ์ œ + ์˜ตํ‹ฐ๋ฏธ์Šคํ‹ฑ ์—…๋ฐ์ดํŠธ
    • ์‹คํŒจ ์‹œ ์บ์‹œ ๋กค๋ฐฑ์œผ๋กœ ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ ๋ณด์žฅ
    • Set ๊ธฐ๋ฐ˜ ์„ ํƒ ์ƒํƒœ ๊ด€๋ฆฌ + ๋ณ‘๋ ฌ ์‚ญ์ œ ์ฒ˜๋ฆฌ
    • ์Šคํฌ๋กค ํˆฌ ํƒ‘ ๋ฒ„ํŠผ + ARIA ์ ‘๊ทผ์„ฑ ์ง€์›
  • ๊ธฐ์‚ฌ๋‹˜ ์ผ์ • ๊ด€๋ฆฌ ํŽ˜์ด์ง€

    • ์›”๋ณ„ ์Šค์ผ€์ค„ ์บ˜๋ฆฐ๋” + ์„ ํƒ ๋‚ ์งœ ์ƒ์„ธ ์ •๋ณด (3:1 ๊ทธ๋ฆฌ๋“œ ๋ ˆ์ด์•„์›ƒ)
    • React Query ์›”๋ณ„ ๋ฐ์ดํ„ฐ ์กฐํšŒ + useMemo ๋‚ ์งœ๋ณ„ ๊ทธ๋ฃนํ™”
    • ์ปค์Šคํ…€ ์บ˜๋ฆฐ๋” ์ปดํฌ๋„ŒํŠธ (date-fns + UTC ๊ธฐ์ค€ ๋‚ ์งœ ์ฒ˜๋ฆฌ)
    • ์Šค์ผ€์ค„ ์ƒํƒœ๋ณ„ ์Šคํƒ€์ผ๋ง + ์ ‘๊ทผ์„ฑ ์ง€์› (ARIA, ์‹œ๋งจํ‹ฑ HTML)
    • ์•ˆ์ „ํ•œ ๋ฒˆ์—ญ ์ฒ˜๋ฆฌ (fallback ๊ธฐ๋ณธ๊ฐ’ + ๋ฒˆ์—ญ ๋กœ๋“œ ์ƒํƒœ ํ™•์ธ)
  • ๋‹ค๊ตญ์–ด ์ฒ˜๋ฆฌ ์‹œ์Šคํ…œ

    • next-intl (useTranslations, useLocale) ๊ธฐ๋ฐ˜ ๋‹ค๊ตญ์–ด ์ง€์›
    • ์–ธ์–ด๋ณ„ ๋‚ ์งœ ํฌ๋งทํŒ… (ํ•œ๊ตญ์–ด: YYYY๋…„ MM์›” DD์ผ, ์˜์–ด: MM/DD/YYYY)
    • ๋ฒˆ์—ญ ์‹คํŒจ ์‹œ ์–ธ์–ด๋ณ„ ๊ธฐ๋ณธ๊ฐ’ ์ฒ˜๋ฆฌ + ๋ฒˆ์—ญ ํ‚ค ๊ฒ€์ฆ
    • ์š”์ผ/์›”๋ช… ๋‹ค๊ตญ์–ด ์ฒ˜๋ฆฌ + UTC ๊ธฐ๋ฐ˜ ํƒ€์ž„์กด ์ด์Šˆ ํ•ด๊ฒฐ

backend

  • ๊ฒฌ์ ์š”์ฒญ ์‹œ์Šคํ…œ

    • ๊ฒฌ์ ์š”์ฒญ CRUD API (์ƒ์„ฑ/์กฐํšŒ/์ˆ˜์ •/์ทจ์†Œ)
    • ๊ถŒํ•œ/ํ”„๋กœํ•„ ํ™•์ธ + ์ž…๋ ฅ ๊ฒ€์ฆ (์ด์‚ฌ์ผ, ์ฃผ์†Œ, ์ด์‚ฌ์œ ํ˜•)
    • Sentry ์—๋Ÿฌ ๋ชจ๋‹ˆํ„ฐ๋ง + ์บ์‹œ ๋ฌดํšจํ™” ์ฒ˜๋ฆฌ
  • ์Šค์ผ€์ค„๋Ÿฌ ์‹œ์Šคํ…œ

    • ๊ธฐ์‚ฌ๋‹˜ ์›”๋ณ„ ์Šค์ผ€์ค„ ์กฐํšŒ API + Redis ์บ์‹ฑ (60์ดˆ TTL)
    • ์ž๋™ํ™” ์Šค์ผ€์ค„๋Ÿฌ: ๋งŒ๋ฃŒ ์ฒ˜๋ฆฌ (00์‹œ), ์•Œ๋ฆผ ์ƒ์„ฑ (09์‹œ)
  • ์ฐœํ•˜๊ธฐ ์‹œ์Šคํ…œ

    • ์ฐœํ•˜๊ธฐ CRUD API + ์†Œํ”„ํŠธ ์‚ญ์ œ/๋ฉฑ๋“ฑ์„ฑ ๋ณด์žฅ
    • DB ๋ชจ๋ธ: ๊ณ ๊ฐ-๊ธฐ์‚ฌ ์ค‘๋ณต ๋ฐฉ์ง€ (@@unique ์ œ์•ฝ)
    • CUSTOMER ๊ถŒํ•œ ์ „์šฉ + ๊ด€๋ จ ์บ์‹œ ๋ฌดํšจํ™”
  • ๋‹ค๊ตญ์–ด ์‹œ์Šคํ…œ

    • ๋ฒˆ์—ญ ๋ฏธ๋“ค์›จ์–ด (?lang=ko|en|zh) + excludeKeys ์ฒ˜๋ฆฌ
    • ๋ฒˆ์—ญ ์‹คํŒจ ์‹œ ์›๋ณธ ์‘๋‹ต + Sentry ๊ธฐ๋ก
  • Redis ์บ์‹ฑ

    • GET ์š”์ฒญ ์บ์‹ฑ + ์‚ฌ์šฉ์ž๋ณ„ ๋ถ„๊ธฐ (varyByAuth)
    • ์บ์‹œ ํ‚ค ๊ทœ์น™ + ์ž๋™ ๋ฌดํšจํ™” ์‹œ์Šคํ…œ
๐Ÿ‘ ๋ฐฑ์ง€์—ฐ

frontend

  • ๊ธฐ์‚ฌ๋‹˜ ์ฐพ๊ธฐ ํŽ˜์ด์ง€

    • ์‹ค์‹œ๊ฐ„ ๋””๋ฐ”์šด์‹ฑ ๊ฒ€์ƒ‰ (100ms ์ง€์—ฐ) + ๋“œ๋กญ๋‹ค์šด ํ•„ํ„ฐ๋ง
    • ๋ฌดํ•œ์Šคํฌ๋กค (react-intersection-observer + useInfiniteQuery) + 4๊ฐœ์”ฉ ํŽ˜์ด์ง€๋„ค์ด์…˜
    • Zustand ์ƒํƒœ ๊ด€๋ฆฌ (๊ฒ€์ƒ‰/ํ•„ํ„ฐ/์ •๋ ฌ)
    • ์ฐœํ•˜๊ธฐ ๊ธฐ๋Šฅ + PC ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์‚ฌ์ด๋“œ๋ฐ”
    • useMemo ์„ฑ๋Šฅ ์ตœ์ ํ™” + Sentry ์—๋Ÿฌ ์ถ”์ 
  • ๊ธฐ์‚ฌ๋‹˜ ์ƒ์„ธ ํŽ˜์ด์ง€

    • ๋™์  ๋ผ์šฐํŒ… (useParams) + ์ƒ์„ธ ์ •๋ณด ์กฐํšŒ
    • ํ”„๋กœํ•„/๊ฒฝ๋ ฅ/ํ‰์ /๋ฆฌ๋ทฐ ์ •๋ณด ํ‘œ์‹œ
    • ์„œ๋น„์Šค ํƒ€์ž…/์ง€์—ญ ๋‹ค๊ตญ์–ด ์ฒ˜๋ฆฌ
    • ๊ฒฌ์ ์š”์ฒญ/์ง€์ •๊ฒฌ์ /์ฐœํ•˜๊ธฐ ์•ก์…˜ ๋ฒ„ํŠผ
    • ์†Œ์…œ ๊ณต์œ  (์นด์นด์˜คํ†ก/ํŽ˜์ด์Šค๋ถ/๋งํฌ)
  • ๋žœ๋”ฉ ํŽ˜์ด์ง€

    • 4๋‹จ๊ณ„ ์„œ๋น„์Šค ์ด์šฉ ๊ณผ์ • ์ธํฌ๊ทธ๋ž˜ํ”ฝ
    • ๋ฐ˜์‘ํ˜• ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ (Next.js Image + WebP)
    • CSS ์• ๋‹ˆ๋ฉ”์ด์…˜ (ํŠธ๋Ÿญ ์Šฌ๋ผ์ด๋“œ์ธ, ํŽ„์Šค ํšจ๊ณผ)
    • ์ ‘๊ทผ์„ฑ (ARIA, ์‹œ๋งจํ‹ฑ HTML, ์Šคํ‚ต ๋งํฌ)

backend

  • ๊ธฐ์‚ฌ๋‹˜ ์กฐํšŒ ์‹œ์Šคํ…œ

    • REST API: ๋ชฉ๋ก/์ƒ์„ธ/์ฐœํ•œ๋ชฉ๋ก ์กฐํšŒ
    • optionalAuth ๋ฏธ๋“ค์›จ์–ด๋กœ ๋กœ๊ทธ์ธ/๋น„๋กœ๊ทธ์ธ ์ง€์›
    • Prisma ๊ธฐ๋ฐ˜ ํŽ˜์ด์ง€๋„ค์ด์…˜/ํ•„ํ„ฐ๋ง/์ •๋ ฌ
  • ๊ฒ€์ƒ‰/ํ•„ํ„ฐ ์‹œ์Šคํ…œ

    • ๋‹‰๋„ค์ž„/์ง€์—ญ ๊ธฐ๋ฐ˜ ํ…์ŠคํŠธ ๊ฒ€์ƒ‰ (OR ์กฐ๊ฑด)
    • ์ง€์—ญ๋ณ„/์„œ๋น„์Šคํƒ€์ž…๋ณ„ ํ•„ํ„ฐ๋ง + DB ์ธ๋ฑ์Šค ์ตœ์ ํ™”
    • ๋ฆฌ๋ทฐ์ˆ˜/ํ‰์ /๊ฒฝ๋ ฅ/์™„๋ฃŒ๊ฑด์ˆ˜ ๊ธฐ์ค€ ์ •๋ ฌ
  • ๋‹ค๊ตญ์–ด/๋ชจ๋‹ˆํ„ฐ๋ง

    • ๋ฒˆ์—ญ ๋ฏธ๋“ค์›จ์–ด (?lang=ko|en|zh)
    • Sentry ์—๋Ÿฌ ์ถ”์  + ์•ก์…˜ ๋กœ๊ทธ
    • ๊ถŒํ•œ ๊ฒ€์ฆ + ๋น„์ฆˆ๋‹ˆ์Šค ๊ทœ์น™ ๊ฒ€์ฆ
๐Ÿ‡ ๊น€์ˆ˜๋นˆ

frontend

  • ์œ ์ € - ๊ฒฌ์  ๊ด€๋ฆฌ ํŽ˜์ด์ง€

    • ์ด์‚ฌ ๊ฒฌ์  ํ™•์ •, ๋ฐ˜๋ ค ๊ธฐ๋Šฅ ๊ตฌํ˜„
    • ๊ฐ„๋‹จํ•œ ๊ฒฌ์ , ์ด์‚ฌ, ๊ธฐ์‚ฌ ์ •๋ณด๊ฐ€ ํฌํ•จ๋œ ์นด๋“œ ๋ฆฌ์ŠคํŠธ UI ๊ตฌํ˜„
    • React Query๋ฅผ ์ด์šฉํ•œ ์ง์ ‘์ ์ธ API ํ˜ธ์ถœ
    • ์œ ํ‹ธํ•จ์ˆ˜๋ฅผ ํ†ตํ•œ ์•ˆ์ •์ ์ธ ๋‹ค๊ตญ์–ด ๋‚ ์งœ ์ฒ˜๋ฆฌ (formatDateWithDay)
  • ๊ธฐ์‚ฌ - ๊ฒฌ์  ๊ด€๋ฆฌ ํŽ˜์ด์ง€

    • ๊ฒฌ์  ๋ฐ›๊ธฐ, ๋ฐ˜๋ ค ๊ธฐ๋Šฅ ๊ตฌํ˜„
    • ๊ฐ„๋‹จํ•œ ๊ฒฌ์ , ์ด์‚ฌ, ๊ธฐ์‚ฌ ์ •๋ณด๊ฐ€ ํฌํ•จ๋œ ์นด๋“œ ๋ฆฌ์ŠคํŠธ UI ๊ตฌํ˜„
    • React Query๋ฅผ ์ด์šฉํ•œ ์ง์ ‘์ ์ธ API ํ˜ธ์ถœ
    • ์œ ํ‹ธํ•จ์ˆ˜๋ฅผ ํ†ตํ•œ ์•ˆ์ •์ ์ธ ๋‹ค๊ตญ์–ด ๋‚ ์งœ ์ฒ˜๋ฆฌ (formatDateWithDay)
    • React ํ›…์„ ์ด์šฉํ•œ ๋น„์ œ์–ด ์ปดํฌ๋„ŒํŠธ ๋ฆฌ๋ Œ๋”๋ง ๋ฐฉ์ง€
  • ๊ณตํ†ต Chip ์ปดํฌ๋„ŒํŠธ

    • ๋ฐ•์Šคํ˜• ๋ฐ ํด๋ฆญ ์—ฌ๋ถ€ ๋“ฑ์„ ํฌํ•จํ•œ ์กฐ๊ฑด๋ถ€ ๋ Œ๋”๋ง์„ ๊ณ ๋ คํ•œ ์„ค๊ณ„
  • SNS ๊ณต์œ  ๊ธฐ๋Šฅ

    • ์นด์นด์˜ค SDK๋ฅผ ์ด์šฉํ•œ ์นด์นด์˜ค API ์‚ฌ์šฉ
    • ํ”ผ๋“œ ํ˜•ํƒœ๋กœ ์ œ๋ชฉ, ์„ค๋ช…, ์ด๋ฏธ์ง€, ๋งํฌ ํฌํ•จ
    • ์‹คํŒจ ์‹œ ํด๋ฆฝ๋ณด๋“œ ๋ณต์‚ฌ๋กœ ๋Œ€์ฒด
    • ํŽ˜์ด์Šค๋ถ ๊ณต์œ  API URL ์‚ฌ์šฉ
    • Open Graph ๋ฉ”ํƒ€ ํƒœ๊ทธ๋ฅผ ๋™์ ์œผ๋กœ ์„ค์ •ํ•˜์—ฌ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ๊ฐœ์„ 
    • ๊ณต์œ  ํ…์ŠคํŠธ๋ฅผ ํด๋ฆฝ๋ณด๋“œ์— ๋ณต์‚ฌํ•˜์—ฌ ์‚ฌ์šฉ์ž ํŽธ์˜์„ฑ ์ œ๊ณต
    • ํŒ์—… ์ฐฝ์œผ๋กœ ๊ณต์œ  ํŽ˜์ด์ง€ ์—ด๊ธฐ

backend

  • ์œ ์ € ๊ฒฌ์  ๊ด€๋ฆฌ API

    • ๊ฒฌ์  ์กฐํšŒ ๋ฐ ํ™•์ •, ๊ฑฐ์ ˆ API ๊ตฌํ˜„
    • MVC ํŒจํ„ด์œผ๋กœ ๋ช…ํ™•ํ•œ ๊ณ„์ธต ๋ถ„๋ฆฌ
    • Redis ์บ์‹ฑ์„ ํ†ตํ•œ ๋น ๋ฅธ ์‘๋‹ต ์†๋„ ๋ฐ DB ๋ถ€ํ•˜ ๊ฐ์†Œ
    • ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ๋กœ ์ธํ•œ ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ ๋ณด์žฅ
  • ๊ธฐ์‚ฌ ๊ฒฌ์  ๊ด€๋ฆฌ API

    • ๊ฒฌ์  ์กฐํšŒ ๋ฐ ๊ฒฌ์  ๋ณด๋‚ด๊ธฐ, ๋ฐ˜๋ คํ•˜๊ธฐ API ๊ตฌํ˜„
    • MVC ํŒจํ„ด์œผ๋กœ ๋ช…ํ™•ํ•œ ๊ณ„์ธต ๋ถ„๋ฆฌ
    • Redis ์บ์‹ฑ์„ ํ†ตํ•œ ๋น ๋ฅธ ์‘๋‹ต ์†๋„ ๋ฐ DB ๋ถ€ํ•˜ ๊ฐ์†Œ
    • ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ๋กœ ์ธํ•œ ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ ๋ณด์žฅ
  • ์Šค์ผ€์ค„๋Ÿฌ ์‹œ์Šคํ…œ

    • node-cron์„ ํ†ตํ•œ ์Šค์ผ€์ค„๋Ÿฌ ์ž‘์„ฑ
    • ์„œ๋ฒ„ ์‹œ์ž‘ ์‹œ ์Šค์ผ€์ค„๋Ÿฌ ์ดˆ๊ธฐํ™” ๋ฐ Sentry๋ฅผ ํ†ตํ•œ ์—๋Ÿฌ ์ถ”์ 
    • ๋ชจ๋“  ํฌ๋ก  ์ž‘์—…์„ ์ค‘์•™ ์ง‘์ค‘์‹์œผ๋กœ ๊ด€๋ฆฌ, ๊ฐ ์ž‘์—…๋ณ„ ๋ช…ํ™•ํ•œ ํ•จ์ˆ˜ ๋ถ„๋ฆฌ
๐ŸŽ ์œค์„ธ์ค€

frontend

  • ๊ณตํ†ต ํŽ˜์ด์ง€๋„ค์ด์…˜ ์‹œ์Šคํ…œ

    • usePagination ์ปค์Šคํ…€ ํ›… + ๋ณต์žกํ•œ ํŽ˜์ด์ง€๋„ค์ด์…˜ ๋กœ์ง
    • ๋ฐ˜์‘ํ˜• ๋””์ž์ธ (sm/lg ํฌ๊ธฐ ์˜ต์…˜)
    • useMemo ์„ฑ๋Šฅ ์ตœ์ ํ™” + ์žฌ์‚ฌ์šฉ์„ฑ ์ œ๊ณต
    • ์—ฌ๋Ÿฌ ํŽ˜์ด์ง€์—์„œ ์ผ๊ด€๋œ ํŽ˜์ด์ง€๋„ค์ด์…˜ ๊ฒฝํ—˜
  • ๊ธฐ์‚ฌ๋‹˜ ๋งˆ์ดํŽ˜์ด์ง€

    • ํ†ตํ•ฉ ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ (์‚ฌ์šฉ์ž ์ •๋ณด + ๊ธฐ์‚ฌ๋‹˜ ์ •๋ณด ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ)
    • ๋ฐ˜์‘ํ˜• ๋ ˆ์ด์•„์›ƒ (๋ฐ์Šคํฌํ†ฑ/๋ชจ๋ฐ”์ผ ์ตœ์ ํ™”)
    • ๋‹จ๊ณ„๋ณ„ ์—๋Ÿฌ ์ƒํƒœ ๊ด€๋ฆฌ + ์•ˆ์ •์ ์ธ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜
  • ๊ธฐ์‚ฌ๋‹˜ ํ”„๋กœํ•„ ์ˆ˜์ • ํŽ˜์ด์ง€

    • Presigned URL ๊ธฐ๋ฐ˜ S3 ์—…๋กœ๋“œ (๋ณด์•ˆ์„ฑ + ์„ฑ๋Šฅ)
    • FileReader API ์‹ค์‹œ๊ฐ„ ์ด๋ฏธ์ง€ ๋ฏธ๋ฆฌ๋ณด๊ธฐ
    • React Hook Form ๋ณต์žกํ•œ ํผ ์ƒํƒœ ๊ด€๋ฆฌ + ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
  • ๊ธฐ์‚ฌ๋‹˜ ๊ธฐ๋ณธ์ •๋ณด ์ˆ˜์ • ํŽ˜์ด์ง€

    • ์กฐ๊ฑด๋ถ€ ๋ Œ๋”๋ง (์ผ๋ฐ˜/์†Œ์…œ ๋กœ๊ทธ์ธ ์‚ฌ์šฉ์ž๋ณ„ UI)
    • ๋™์  ๋ ˆ์ด์•„์›ƒ (์‚ฌ์šฉ์ž ํƒ€์ž…๋ณ„ 1์—ด/2์—ด ์ž๋™ ์ „ํ™˜)
    • ๋กœ๊ทธ์ธ ๋ฐฉ์‹๋ณ„ ์กฐ๊ฑด๋ถ€ ๊ฒ€์ฆ ๋กœ์ง

backend

  • ํŽ˜์ด์ง€๋„ค์ด์…˜ API

    • ํ‘œ์ค€ ํŽ˜์ด์ง€๋„ค์ด์…˜ API ๊ตฌ์กฐ
    • ํŽ˜์ด์ง€ ๊ธฐ๋ฐ˜ ๋ฐ์ดํ„ฐ ์กฐํšŒ ์‹œ์Šคํ…œ
  • ๊ธฐ์‚ฌ๋‹˜ ๋งˆ์ดํŽ˜์ด์ง€ API

    • ํ†ตํ•ฉ API ์„ค๊ณ„ (ํ•„์š”ํ•œ ๋ชจ๋“  ๋ฐ์ดํ„ฐ ํšจ์œจ์  ์กฐํšŒ)
    • Prisma ๋ณต์žกํ•œ ๊ด€๊ณ„ ๋ฐ์ดํ„ฐ ์กฐํšŒ ์ตœ์ ํ™”
  • ๊ธฐ์‚ฌ๋‹˜ ํ”„๋กœํ•„ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ

    • S3 ํด๋ผ์ด์–ธํŠธ ์•ˆ์ „ํ•œ ํŒŒ์ผ ์—…๋กœ๋“œ
    • ํ”„๋กœํ•„ ๋ฐ์ดํ„ฐ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ + ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง
  • ๊ธฐ์‚ฌ๋‹˜ ๊ธฐ๋ณธ์ •๋ณด ๊ด€๋ฆฌ ์‹œ์Šคํ…œ

    • ์‚ฌ์šฉ์ž ํƒ€์ž…๋ณ„ ์ฒ˜๋ฆฌ (์ผ๋ฐ˜/์†Œ์…œ ๋กœ๊ทธ์ธ ๋ถ„๋ฆฌ)
    • bcrypt ๋น„๋ฐ€๋ฒˆํ˜ธ ํ•ด์‹ฑ + ๊ฒ€์ฆ
    • ๋ฏผ๊ฐ์ •๋ณด ์•”ํ˜ธํ™” ์ €์žฅ (์ „ํ™”๋ฒˆํ˜ธ ๋“ฑ)
๐ŸŠ ๋ฐ•๋ฏผ๊ทœ

frontend

  • ๋กœ๊ทธ์ธ/ํšŒ์›๊ฐ€์ž… ํŽ˜์ด์ง€

    • ์ด์ค‘ ์‚ฌ์šฉ์ž ํƒ€์ž… ์ง€์› (CUSTOMER/MOVER ๋ณ„๋„ ํŽ˜์ด์ง€)
    • ์†Œ์…œ ๋กœ๊ทธ์ธ: Google, Kakao, Naver OAuth ์—ฐ๋™
    • React Hook Form + ์‹ค์‹œ๊ฐ„ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
    • 14๋ถ„ ์ฃผ๊ธฐ ์ž๋™ ํ† ํฐ ๊ฐฑ์‹ , ์ค‘๋ณต ๊ฐฑ์‹  ๋ฐฉ์ง€
    • next-intl ๊ธฐ๋ฐ˜ ๋‹ค๊ตญ์–ด ์ง€์› + ARIA ์ ‘๊ทผ์„ฑ
  • ํ”„๋กœํ•„ ๋“ฑ๋ก ํŽ˜์ด์ง€

    • ์‚ฌ์šฉ์ž ํƒ€์ž…๋ณ„ ๋งž์ถค ํผ (CUSTOMER/MOVER)
    • S3 ์ง์ ‘ ์—…๋กœ๋“œ + CloudFront URL ์ƒ์„ฑ
    • ํŒŒ์ผ๋ช… ํƒ€์ž„์Šคํƒฌํ”„ ๋ณ€๊ฒฝ์œผ๋กœ ๋ณด์•ˆ ๊ฐ•ํ™”
    • 17๊ฐœ ์‹œ๋„/3๊ฐœ ์„œ๋น„์Šค ํƒ€์ž… ์„ ํƒ UI
    • ๊ธ€์ž ์ˆ˜ ์นด์šดํ„ฐ + ์‹ค์‹œ๊ฐ„ ๊ฒ€์ฆ
  • ํ”„๋กœํ•„ ์ˆ˜์ • ํŽ˜์ด์ง€

    • ๊ธฐ์กด ์ •๋ณด ์ž๋™ ๋กœ๋”ฉ + ํƒ€์ž…๋ณ„ ๋ถ„๊ธฐ ์ฒ˜๋ฆฌ
    • ์†Œ์…œ ๋กœ๊ทธ์ธ ๊ณ„์ • ๋ณด์•ˆ ์ฒ˜๋ฆฌ (์ด๋ฉ”์ผ ์ฝ๊ธฐ์ „์šฉ, ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ ์ œํ•œ)
    • ํ˜„์žฌ/์ƒˆ ๋น„๋ฐ€๋ฒˆํ˜ธ ๊ต์ฐจ ๊ฒ€์ฆ
    • ์ˆ˜์ • ์™„๋ฃŒ ํ›„ ์ „์—ญ ์‚ฌ์šฉ์ž ์ •๋ณด ๊ฐฑ์‹ 

backend

  • ์ธ์ฆ ์‹œ์Šคํ…œ

    • JWT ๊ธฐ๋ฐ˜ ์ด์ค‘ ํ† ํฐ (Access 15๋ถ„ + Refresh 2์ฃผ)
    • ์Šฌ๋ผ์ด๋”ฉ ์„ธ์…˜: Refresh Token ๋งŒ๋ฃŒ 5์ผ ์ „ ์ž๋™ ์žฌ๋ฐœ๊ธ‰
    • Passport.js ๊ธฐ๋ฐ˜ ์†Œ์…œ ๋กœ๊ทธ์ธ (Google, Kakao, Naver)
    • ์ฟ ํ‚ค ๊ธฐ๋ฐ˜ ํ† ํฐ ์ €์žฅ (httpOnly, secure)
  • ๋ณด์•ˆ ์‹œ์Šคํ…œ

    • bcrypt ๋น„๋ฐ€๋ฒˆํ˜ธ ํ•ด์‹ฑ (salt rounds: 10)
    • ์ „ํ™”๋ฒˆํ˜ธ AES-256-CBC ์•”ํ˜ธํ™” ์ €์žฅ
    • Rate Limiting: ๋กœ๊ทธ์ธ/ํšŒ์›๊ฐ€์ž… ์š”์ฒญ ์ œํ•œ
    • ๋ฏธ๋“ค์›จ์–ด: verifyAccessToken, verifyRefreshToken, optionalAuth
  • ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ

    • ์ด๋ฉ”์ผ: ํ—ˆ์šฉ TLD ๋ฆฌ์ŠคํŠธ ๊ธฐ๋ฐ˜ ๊ฒ€์ฆ
    • ๋น„๋ฐ€๋ฒˆํ˜ธ: 8์ž ์ด์ƒ, ์˜๋ฌธ/์ˆซ์ž/ํŠน์ˆ˜๋ฌธ์ž ์กฐํ•ฉ
    • ์ „ํ™”๋ฒˆํ˜ธ: ํ•œ๊ตญ ํ˜•์‹ (010xxxxxxxx) ๊ฒ€์ฆ
    • ์ด๋ฆ„: ํ•œ๊ธ€/์˜๋ฌธ/์ค‘๊ตญ์–ด 1-15์ž

๐Ÿ“ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

public/
โ”œโ”€โ”€ img/                         # ์ •์  ์ด๋ฏธ์ง€(README, ๋กœ๊ณ  ๋“ฑ)
โ”œโ”€โ”€ favicon.ico
โ””โ”€โ”€ og-image.png

src/
โ”œโ”€โ”€ app/                         # App Router ์—”ํŠธ๋ฆฌ(์„œ๋ฒ„/ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ)
โ”‚   โ”œโ”€โ”€ [locale]/                # ๋‹ค๊ตญ์–ด ๋ผ์šฐํŒ… ๋ฃจํŠธ(next-intl)
โ”‚   โ”‚   โ”œโ”€โ”€ (auth)/              # ๋กœ๊ทธ์ธ/ํšŒ์›๊ฐ€์ž… ๋“ฑ ์ธ์ฆ ํ”Œ๋กœ์šฐ
โ”‚   โ”‚   โ”œโ”€โ”€ estimate/            # ๊ฒฌ์  ๊ด€๋ จ ํŽ˜์ด์ง€
โ”‚   โ”‚   โ”œโ”€โ”€ estimateRequest/     # ๊ฒฌ์  ์š”์ฒญ ๊ด€๋ จ ํŽ˜์ด์ง€
โ”‚   โ”‚   โ”œโ”€โ”€ favoriteMover/       # ์ฆ๊ฒจ์ฐพ๊ธฐํ•œ ๊ธฐ์‚ฌ๋‹˜
โ”‚   โ”‚   โ”œโ”€โ”€ moverMyPage/         # ๊ธฐ์‚ฌ๋‹˜ ๋งˆ์ดํŽ˜์ด์ง€
โ”‚   โ”‚   โ”œโ”€โ”€ profile/             # ํ”„๋กœํ•„(๋“ฑ๋ก/์ˆ˜์ •)
โ”‚   โ”‚   โ”œโ”€โ”€ review/              # ๋ฆฌ๋ทฐ ์ž‘์„ฑ/์กฐํšŒ
โ”‚   โ”‚   โ”œโ”€โ”€ searchMover/         # ๊ธฐ์‚ฌ๋‹˜ ๊ฒ€์ƒ‰/์ƒ์„ธ
โ”‚   โ”‚   โ”œโ”€โ”€ layout.tsx
โ”‚   โ”‚   โ””โ”€โ”€ providers.tsx
โ”‚   โ”œโ”€โ”€ error.tsx
โ”‚   โ”œโ”€โ”€ globals.css
โ”‚   โ””โ”€โ”€ layout.tsx
โ”œโ”€โ”€ assets/                      # ๊ฐœ๋ฐœ์šฉ ์—์…‹
โ”œโ”€โ”€ components/                  # ๋„๋ฉ”์ธ/๊ณตํ†ต UI ์ปดํฌ๋„ŒํŠธ
โ”œโ”€โ”€ constant/                    # ์ƒ์ˆ˜ ๋ชจ์Œ
โ”œโ”€โ”€ hooks/                       # ์ปค์Šคํ…€ ํ›…
โ”œโ”€โ”€ i18n/                        # ๊ตญ์ œํ™” ๋ผ์šฐํŒ…/๋ฉ”์‹œ์ง€ ํ‚ค
โ”œโ”€โ”€ layout/                      # ๋ ˆ์ด์•„์›ƒ ๊ด€๋ จ ๋ชจ๋“ˆ(๋ณด์กฐ)
โ”œโ”€โ”€ lib/                         # API/์„œ๋ฒ„ ์•ก์…˜/์œ ํ‹ธ ๋“ฑ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๊ณ„์ธต
โ”‚   โ”œโ”€โ”€ actions/                 # ์„œ๋ฒ„ ์•ก์…˜(์ฟ ํ‚ค ๋“ฑ ์„œ๋ฒ„ ์ „์šฉ ๋กœ์ง)
โ”‚   โ”œโ”€โ”€ api/                     # API ํด๋ผ์ด์–ธํŠธ(ํ† ํฐ ๊ฐฑ์‹  ํฌํ•จ)
โ”‚   โ””โ”€โ”€ utils/                   # ๊ณต์šฉ ์œ ํ‹ธ ํ•จ์ˆ˜
โ”œโ”€โ”€ messages/                    # ๋‹ค๊ตญ์–ด ๋ฒˆ์—ญ ๋ฉ”์‹œ์ง€(ko/en/zh)
โ”œโ”€โ”€ pageComponents/              # ํŽ˜์ด์ง€ ์กฐ๋ฆฝ์šฉ ์ปจํ…Œ์ด๋„ˆ ์ปดํฌ๋„ŒํŠธ
โ”œโ”€โ”€ providers/                   # ์ „์—ญ Provider(Auth/Query ๋“ฑ)
โ”œโ”€โ”€ services/                    # ๋„๋ฉ”์ธ ์„œ๋น„์Šค(๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง)
โ”œโ”€โ”€ stores/                      # ์ „์—ญ ์ƒํƒœ(zustand)
โ”œโ”€โ”€ types/                       # ์ „์—ญ ํƒ€์ž… ์ •์˜
โ”œโ”€โ”€ utils/                       # ๋ฒ”์šฉ ์œ ํ‹ธ ํ•จ์ˆ˜
โ”œโ”€โ”€ instrumentation-client.ts    # Sentry ๋“ฑ ํด๋ผ์ด์–ธํŠธ ๊ณ„์ธก ์„ค์ •
โ”œโ”€โ”€ instrumentation.ts           # Sentry ๋“ฑ ์„œ๋ฒ„ ๊ณ„์ธก ์„ค์ •
โ””โ”€โ”€ middleware.ts                # Next ๋ฏธ๋“ค์›จ์–ด(i18n/ํ† ํฐ ์กด์žฌ ๋“ฑ)

๐ŸŒŸ ์ฃผ์š” ๊ธฐ๋Šฅ ์ƒ์„ธ

1. ์ธ์ฆ ์‹œ์Šคํ…œ
  • ์œ ์ € ํƒ€์ž…๋ณ„ ๋กœ๊ทธ์ธ/ํšŒ์›๊ฐ€์ž…

    • ์ผ๋ฐ˜ ์œ ์ € / ๊ธฐ์‚ฌ๋‹˜ ๋ณ„๋„ ํŽ˜์ด์ง€ ๋ฐ ๊ถŒํ•œ ๋ถ€์—ฌ
    • ์ผ๋ฐ˜ ๊ฐ€์ž… ์‹œ ์ด๋ฆ„/์ด๋ฉ”์ผ/์ „ํ™”๋ฒˆํ˜ธ/๋น„๋ฐ€๋ฒˆํ˜ธ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
  • ์†Œ์…œ ๋กœ๊ทธ์ธ/ํšŒ์›๊ฐ€์ž…

    • Google ยท Naver ยท Kakao ์ง€์›
  • ์œ ์ € ํƒ€์ž… ๋ณ€๊ฒฝ

    • ์ผ๋ฐ˜์œ ์ € โ†” ๊ธฐ์‚ฌ๋‹˜ ID ์ „ํ™˜
    • ๋ฐ˜๋Œ€ํŽธ ๊ณ„์ •์œผ๋กœ ๋กœ๊ทธ์•„์›ƒ ์—†์ด ์ „ํ™˜ ๊ฐ€๋Šฅ
  • ๋กœ๊ทธ์•„์›ƒ

    • DB ๋ฆฌํ”„๋ ˆ์‰ฌ ํ† ํฐ ์‚ญ์ œ ๋ฐ ํด๋ผ์ด์–ธํŠธ ์ฟ ํ‚ค ์‚ญ์ œ
  • ๋ฆฌํ”„๋ ˆ์‰ฌ ํ† ํฐ ๊ฐฑ์‹ 

    • JWT ์Šฌ๋ผ์ด๋”ฉ ์„ธ์…˜ ๊ธฐ๋ฐ˜ ๋ฆฌํ”„๋ ˆ์‰ฌ ํ† ํฐ ๊ฐฑ์‹  ๋กœ์ง (ํ˜„์žฌ 14๋ถ„ ๋งˆ๋‹ค ์„ ์ œ ๊ฐฑ์‹  ์š”์ฒญ์ค‘, ๋งŒ๋ฃŒ๋Š” 15๋ถ„)
  • ์š”์ฒญ ์ œํ•œ

    • (express-rate-limit๋ฅผ) ์ด์šฉํ•œ ๋ฌด์ฐจ๋ณ„ ๋Œ€์ž… ๊ณต๊ฒฉ ๋ฐฉ์–ด๋ฅผ ์œ„ํ•œ API ์†๋„ ์ œํ•œ ์ ์šฉ
  • ํ† ์ŠคํŠธ ๋ชจ๋‹ฌ ์ถœ๋ ฅ

    • ์„ฑ๊ณต/๊ฒฝ๊ณ /์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ๊ณ ์ • ํ…์ŠคํŠธ๋กœ ๋ฐฑ์—”๋“œ์—์„œ ์ „๋‹ฌ๋ฐ›์•„ ํ”„๋ก ํŠธ์—”๋“œ์—์„œ ์ง์ ‘ ๋ฒˆ์—ญ ์ฒ˜๋ฆฌ(์ •์  ํ…์ŠคํŠธ์ด๊ธฐ ๋•Œ๋ฌธ, deepL ์‚ฌ์šฉ์˜ ์ตœ์†Œํ™”๋ฅผ ์œ„ํ•จ)
2. ํ”„๋กœํ•„ ๋“ฑ๋ก/์ˆ˜์ •
  • ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ(UI)

    • ์ด๋ฏธ์ง€ ํŒŒ์ผ๋งŒ ํ—ˆ์šฉ, ์„ ํƒ ์‹œ ํƒ€์ž„์Šคํƒฌํ”„ ๋ฆฌ๋„ค์ด๋ฐ(์„œ๋ช… ๋ถˆ์ผ์น˜ ๋ฐฉ์ง€)
    • S3 ์—…๋กœ๋“œ ํ›„ ์ฆ‰์‹œ ๋ฏธ๋ฆฌ๋ณด๊ธฐ, ์Šค์ผˆ๋ ˆํ†ค โ†’ ์‹ค์ œ ์ด๋ฏธ์ง€(LCP ์šฐ์„ )
    • CloudFront CDN์œผ๋กœ ์ „์—ญ ์—ฃ์ง€ ์บ์‹ฑ ์„œ๋น™
  • ํผ UI(ํ…์ŠคํŠธ/์„ ํƒ/ํŽธ์ง‘)

    • ํ…์ŠคํŠธ: ํ•œ ์ค„ ์†Œ๊ฐœ(์ตœ๋Œ€ 30์ž), ์ƒ์„ธ ์†Œ๊ฐœ(์ตœ๋Œ€ 300์ž) ๊ทธ์™ธ ๋‹ค์–‘ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ํผ
    • ์„ ํƒ: ์„œ๋น„์Šค(๋‹ค์ค‘ ํ† ๊ธ€) ยท ์ง€์—ญ(๋‹จ์ผ/๋‹ค์ค‘, ๋กœ์ผ€์ผ๋ณ„ ๊ทธ๋ฆฌ๋“œ)
    • ํŽธ์ง‘: ๊ธฐ์กด ๊ฐ’ ํ”„๋ฆฌํ•„, ์ทจ์†Œ/์ €์žฅ ๋ฒ„ํŠผ๊ณผ ๋น„ํ™œ์„ฑํ™” ์ƒํƒœ ์ œ๊ณต
  • ํ”„๋กœํ•„ ์กฐํšŒ api ์บ์‹ฑ

    • ์บ์‹œ ๋ฏธ๋“ค์›จ์–ด: (cacheMiddleware.ts)๋ฅผ ํ†ตํ•œ ์ž๋™ ์บ์‹ฑ ์ฒ˜๋ฆฌ
    • ๊ธฐ์กด ์‘๋‹ต ์†๋„ ํ‰๊ท  80ms ์—์„œ ํ‰๊ท  25ms๋กœ, 68.75%์˜ ์„ฑ๋Šฅ ๊ฐœ์„  ํ™•์ธ
  • ํ† ์ŠคํŠธ ๋ชจ๋‹ฌ ์ถœ๋ ฅ

    • ์„ฑ๊ณต/๊ฒฝ๊ณ /์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ๊ณ ์ • ํ…์ŠคํŠธ๋กœ ๋ฐฑ์—”๋“œ์—์„œ ์ „๋‹ฌ๋ฐ›์•„ ํ”„๋ก ํŠธ์—”๋“œ์—์„œ ์ง์ ‘ ๋ฒˆ์—ญ ์ฒ˜๋ฆฌ(์ •์  ํ…์ŠคํŠธ์ด๊ธฐ ๋•Œ๋ฌธ, deepL ์‚ฌ์šฉ์˜ ์ตœ์†Œํ™”๋ฅผ ์œ„ํ•จ)
3. ๊ฒฌ์  ์š”์ฒญ
  • ์ฑ„ํŒ…ํ˜• ์ด์‚ฌ ์ •๋ณด ์ž…๋ ฅ
    • ์ด์‚ฌ์ข…๋ฅ˜/๋‚ ์งœ/์ถœยท๋„์ฐฉ์ง€
  • ์ฃผ์†Œ ์ž…๋ ฅ
    • ์นด์นด์˜ค ์šฐํŽธ๋ฒˆํ˜ธ ์„œ๋น„์Šค ์ ์šฉ
  • ํ”„๋กœ๊ทธ๋ž˜์Šค ๋ฐ”
    • ๊ฐ ๋‹จ๊ณ„ ํ‘œ์‹œ, ๊ฐ ํ•ญ๋ชฉ ์ˆ˜์ • ๊ฐ€๋Šฅ
  • ์ปค์Šคํ…€ ๋‹ฌ๋ ฅ UI
    • (date-fns) ๊ธฐ๋ฐ˜ ๋งคํŠธ๋ฆญ์Šค/์›” ์ „ํ™˜, ํ•œ๊ตญ๋‚ ์งœ ๊ธฐ์ค€ ๋‚ ์งœ ์ƒ์„ฑ(ํƒ€์ž„์กด ์ด์Šˆ ํšŒํ”ผ), ๊ณผ๊ฑฐยท์˜ค๋Š˜ ๋น„ํ™œ์„ฑํ™”, ๋‹ค๊ตญ์–ด ์š”์ผ ์ง€์›
  • ์š”์ฒญ ์ œํ•œ ๊ทœ์น™
    • ์ด์‚ฌ์ผ ๊ฒฝ๊ณผ ํ›„ ์ƒˆ ์š”์ฒญ ๊ฐ€๋Šฅ
    • ํ™œ์„ฑ ์š”์ฒญ 1๊ฑด๋งŒ ์œ ์ง€
    • ๊ฒฌ์  ์ˆ˜: ์ตœ๋Œ€ 5๊ฐœ
  • ์š”์ฒญ ์ œํ•œ
    • express-rate-limit๋ฅผ ์ด์šฉํ•œ ๋ฌด์ฐจ๋ณ„ ๋Œ€์ž… ๊ณต๊ฒฉ ๋ฐฉ์–ด๋ฅผ ์œ„ํ•œ API ์†๋„ ์ œํ•œ ์ ์šฉ
  • ๊ฒฌ์  ์กฐํšŒ api ์บ์‹ฑ
    • ์บ์‹œ ๋ฏธ๋“ค์›จ์–ด: (cacheMiddleware.ts)๋ฅผ ํ†ตํ•œ ์ž๋™ ์บ์‹ฑ ์ฒ˜๋ฆฌ
    • ๊ธฐ์กด ์‘๋‹ต ์†๋„ ํ‰๊ท  155ms ์—์„œ ํ‰๊ท  6ms๋กœ, 96.13%์˜ ์„ฑ๋Šฅ ๊ฐœ์„  ํ™•์ธ
  • ํผ ์ดํƒˆ ๋ฐฉ์ง€
    • ์ปค์Šคํ…€ ํ›…(useUnsavedChangesGuard)์œผ๋กœ ์ƒˆ๋กœ๊ณ ์นจ(beforeunload)ยท๋’ค๋กœ๊ฐ€๊ธฐ(popstate)ยท๋งํฌ ํด๋ฆญยทF5/Ctrl+R์„ ๊ฐ€๋กœ์ฑ„ ํ™•์ธ ๋ชจ๋‹ฌ๋กœ ์ฐจ๋‹จํ•˜๊ณ , ์ œ์ถœ ์‹œ bypassFor๋กœ ์ผ์‹œ ํ•ด์ œ
  • ํ† ์ŠคํŠธ ๋ชจ๋‹ฌ ์ถœ๋ ฅ
    • ์„ฑ๊ณต/๊ฒฝ๊ณ /์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ๊ณ ์ • ํ…์ŠคํŠธ๋กœ ๋ฐฑ์—”๋“œ์—์„œ ์ „๋‹ฌ๋ฐ›์•„ ํ”„๋ก ํŠธ์—”๋“œ์—์„œ ์ง์ ‘ ๋ฒˆ์—ญ ์ฒ˜๋ฆฌ(์ •์  ํ…์ŠคํŠธ์ด๊ธฐ ๋•Œ๋ฌธ, deepL ์‚ฌ์šฉ์˜ ์ตœ์†Œํ™”๋ฅผ ์œ„ํ•จ)
3. ๊ธฐ์‚ฌ๋‹˜ ์ฐพ๊ธฐ
  • ๊ฒ€์ƒ‰/ํ•„ํ„ฐ/์ •๋ ฌ ์‹œ์Šคํ…œ

    • ์‹ค์‹œ๊ฐ„ ๋””๋ฐ”์šด์‹ฑ ๊ฒ€์ƒ‰(๋ณ„๋ช…/์ง€์—ญ), ๊ฒ€์ƒ‰์–ด ์ดˆ๊ธฐํ™” ๋ฒ„ํŠผ
    • ์ง€์—ญ/์„œ๋น„์Šค ํƒ€์ž… ๋“œ๋กญ๋‹ค์šด ํ•„ํ„ฐ, ์ „์ฒด ํ•„ํ„ฐ ์ดˆ๊ธฐํ™” ์ง€์›
    • ์ •๋ ฌ: ๋ฆฌ๋ทฐยทํ‰์ ยท๊ฒฝ๋ ฅยทํ™•์ • ํšŸ์ˆ˜ ๊ธฐ์ค€, zustand๋กœ ์ƒํƒœ ๊ด€๋ฆฌ
  • ๋ฌดํ•œ ์Šคํฌ๋กค ๋ชฉ๋ก

    • useInfiniteQuery + react-intersection-observer๋กœ ๊ตฌํ˜„
    • 4๊ฐœ์”ฉ ํŽ˜์ด์ง€๋„ค์ด์…˜, ์Šคํฌ๋กค 500px ์ด์ƒ ์‹œ ๋งจ ์œ„๋กœ ๊ฐ€๊ธฐ ๋ฒ„ํŠผ ํ•˜๋‹จ์— ์ถœํ˜„
    • ์Šค์ผˆ๋ ˆํ†ค ๋กœ๋”ฉ, ์—๋Ÿฌ ์ƒํƒœ ์ฒ˜๋ฆฌ, ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์—†์Œ ์•ˆ๋‚ด
  • ๊ธฐ์‚ฌ๋‹˜ ์นด๋“œ ์ •๋ณด

    • ํ”„๋กœํ•„ ์ด๋ฏธ์ง€, ๋ณ„๋ช…, ํ•œ ์ค„ ์†Œ๊ฐœ, ํ‰์ /๋ฆฌ๋ทฐ ์ˆ˜/๊ฒฝ๋ ฅ/์™„๋ฃŒ ๊ฑด์ˆ˜
    • 30๊ฑด ์ด์ƒ ์™„๋ฃŒ ์‹œ ์ „๋ฌธ ๊ธฐ์‚ฌ ๋ฐฐ์ง€ ํ‘œ์‹œ
    • ๋ชจ๋ฐ”์ผ/๋ฐ์Šคํฌํ†ฑ ๋ฐ˜์‘ํ˜• ๋ ˆ์ด์•„์›ƒ, ์ฐœํ•˜๊ธฐ ๋ฒ„ํŠผ ํฌํ•จ
  • ์ƒ์„ธ ํŽ˜์ด์ง€

    • ๊ธฐ์‚ฌ๋‹˜ ์†Œ๊ฐœ: ํ”„๋กœํ•„/๊ฒฝ๋ ฅ/ํ‰์ /์ฐœ ์ˆ˜, ์ƒ์„ธ ์†Œ๊ฐœ ๋”๋ณด๊ธฐ/์ ‘๊ธฐ
    • ์„œ๋น„์Šค/์ง€์—ญ ์ •๋ณด ์นฉ, ๋ฆฌ๋ทฐ ํ‰์  ์š”์•ฝ ๋ฐ ๊ณ ๊ฐ ๋ฆฌ๋ทฐ ๋ชฉ๋ก
    • ๊ฒฌ์  ์š”์ฒญ/์ง€์ • ๊ฒฌ์  ๋ฒ„ํŠผ, ์†Œ์…œ ๊ณต์œ (์นด์นด์˜คํ†ก/ํŽ˜์ด์Šค๋ถ/๋งํฌ)
    • aria-label, role ๋“ฑ ์›น ์ ‘๊ทผ์„ฑ ์ง€์›, ์Šคํ‚ต ๋งํฌ ์ œ๊ณต
  • ์ฐœํ•˜๊ธฐ/๊ณต์œ  ๊ธฐ๋Šฅ

    • ๋กœ๊ทธ์ธ ํ•„์š”, React Query๋กœ ์บ์‹œ ์ž๋™ ๊ฐฑ์‹ 
    • PC์—์„œ ์šฐ์ธก ์‚ฌ์ด๋“œ๋ฐ”์— ์ฐœํ•œ ๊ธฐ์‚ฌ๋‹˜ ์ตœ๋Œ€ 3๋ช… ๋ฏธ๋ฆฌ๋ณด๊ธฐ
    • ๊ณต์œ  ๋ฉ”์‹œ์ง€ ์ž๋™ ์ƒ์„ฑ(๊ธฐ์‚ฌ๋‹˜ ์ •๋ณด + ์ถ”์ฒœ ๋ฉ˜ํŠธ), ๋ชจ๋ฐ”์ผ/๋ฐ์Šคํฌํ†ฑ ์œ„์น˜ ์ฐจ๋ณ„ํ™” ๋ฐฉ์ง€
4. ๋‚ด ๊ฒฌ์  ๊ด€๋ฆฌ (์ผ๋ฐ˜ ์œ ์ €)
  • ๋Œ€๊ธฐ ์ค‘์ธ ๊ฒฌ์ 

    • ํ˜„์žฌ ๊ฒฌ์  ์š”์ฒญ ์ •๋ณด ํ‘œ์‹œ: ์ด์‚ฌ ํƒ€์ž…, ๋‚ ์งœ, ์ถœ๋ฐœ์ง€/๋„์ฐฉ์ง€ ์ฃผ์†Œ
    • ๋ฐ›์€ ๊ฒฌ์  ์นด๋“œ ๋ชฉ๋ก: ๊ธฐ์‚ฌ๋‹˜ ์ •๋ณด, ๊ฐ€๊ฒฉ, ์„œ๋น„์Šค ๋‚ด์šฉ
    • ๊ฒฌ์  ์นด๋“œ ์ด๋ฒคํŠธ: ์ฐœํ•˜๊ธฐ ํ† ๊ธ€, ๊ฒฌ์  ํ™•์ •, ์ƒ์„ธ๋ณด๊ธฐ ํด๋ฆญ์œผ๋กœ ์ƒ์„ธํŽ˜์ด์ง€ ์ด๋™
  • ๋ฐ›์•˜๋˜ ๊ฒฌ์  (์™„๋ฃŒ ๋‚ด์—ญ)

    • ๊ณผ๊ฑฐ ๊ฒฌ์  ์š”์ฒญ๋ณ„๋กœ ๊ทธ๋ฃนํ•‘: ๊ฐ ์ด์‚ฌ ๊ฑด๋ณ„ ๊ฒฌ์  ์š”์ฒญ โ†’ ๋ฐ›์€ ๊ฒฌ์ ๋“ค ํ‘œ์‹œ
    • ๊ฒฌ์  ์ƒํƒœ: ํ™•์ •๋จ/๋งŒ๋ฃŒ๋จ/๊ฑฐ์ ˆ๋จ ๋“ฑ ์ตœ์ข… ์ƒํƒœ ํ‘œ์‹œ
    • ๋ฐ์ดํ„ฐ ์—†์„ ์‹œ: "์™„๋ฃŒ๋œ ์ด์‚ฌ ํ›„ ํ™•์ธ ๊ฐ€๋Šฅ" ์•ˆ๋‚ด + ์ผ๋Ÿฌ์ŠคํŠธ๋ ˆ์ด์…˜
    • ์ด์‚ฌ ์™„๋ฃŒ ์ฒ˜๋ฆฌ: "์ด์‚ฌ ์™„๋ฃŒ" ๋ฒ„ํŠผ์œผ๋กœ ๋ฆฌ๋ทฐ ์ž‘์„ฑ ํŽ˜์ด์ง€ ์ด๋™
  • ๊ฒฌ์  ์ƒํƒœ๋ณ„ ์•ก์…˜

    • PROPOSED: ํ™•์ • ๋ฒ„ํŠผ ํ™œ์„ฑํ™” (๋‹ค๋ฅธ ๊ฒฌ์  ํ™•์ • ์‹œ ์ž๋™ ๋น„ํ™œ์„ฑํ™”)
    • ACCEPTED: "์ด๋ฏธ ํ™•์ •๋จ" ํ‘œ์‹œ, ํ™•์ • ์•„์ด์ฝ˜, ์ฐœํ•˜๊ธฐ/๊ณต์œ  ๋ฒ„ํŠผ๋งŒ ํ™œ์„ฑ
    • AUTO_REJECTED/EXPIRED/REJECTED: ๋น„ํ™œ์„ฑ ๋ฒ„ํŠผ, ์ƒํƒœ๋ณ„ ์•ˆ๋‚ด ๋ฉ”์‹œ์ง€
  • ๋Œ€๊ธฐ ์ค‘์ธ ๊ฒฌ์  ์กฐํšŒ api ์บ์‹ฑ

    • ์บ์‹œ ๋ฏธ๋“ค์›จ์–ด: (cacheMiddleware.ts)๋ฅผ ํ†ตํ•œ ์ž๋™ ์บ์‹ฑ ์ฒ˜๋ฆฌ
    • ๊ธฐ์กด ์‘๋‹ต ์†๋„ ํ‰๊ท  130ms ์—์„œ ํ‰๊ท  29ms๋กœ, 77.69%์˜ ์„ฑ๋Šฅ ๊ฐœ์„  ํ™•์ธ
5. ๋‚ด ๊ฒฌ์  ๊ด€๋ฆฌ (๊ธฐ์‚ฌ๋‹˜)
  • ๋ณด๋‚ธ ๊ฒฌ์  ๋ชฉ๋ก

    • ๋ณด๋‚ธ ๊ฒฌ์  ๋‚ด์—ญ: ๊ณ ๊ฐ ์ •๋ณด, ์ด์‚ฌ ์ •๋ณด, ๊ฒฌ์  ๊ฐ€๊ฒฉ, ๊ฒฌ์  ์ƒํƒœ
    • ๊ฒฌ์  ์ƒํƒœ๋ณ„ ํ‘œ์‹œ: PROPOSED(๋Œ€๊ธฐ ์ค‘), ACCEPTED(ํ™•์ •๋จ), AUTO_REJECTED(์ž๋™ ๋ฐ˜๋ ค)
    • ๊ณผ๊ฑฐ ๋‚ ์งœ๋‚˜ ๋ฐ˜๋ ค๋œ ๊ฒฌ์ ์€ ์˜ค๋ฒ„๋ ˆ์ด ์ฒ˜๋ฆฌ, ์ƒ์„ธ๋ณด๊ธฐ๋งŒ ๊ฐ€๋Šฅ
    • 2์—ด ๊ทธ๋ฆฌ๋“œ ๋ ˆ์ด์•„์›ƒ(๋ฐ์Šคํฌํ†ฑ), ๋ชจ๋ฐ”์ผ์—์„œ 1์—ด
  • ๋ฐ˜๋ คํ•œ ๊ฒฌ์  ๋ชฉ๋ก

    • ๋‚ด๊ฐ€ ๋ฐ˜๋ คํ•œ ๊ฒฌ์  ์š”์ฒญ ๋‚ด์—ญ
    • ๋ฐ˜๋ ค ์‚ฌ์œ ์™€ ํ•จ๊ป˜ ํ‘œ์‹œ, ์ „์ฒด ์นด๋“œ ์–ด๋‘์šด ์˜ค๋ฒ„๋ ˆ์ด ์ฒ˜๋ฆฌ
    • ๋ฐ์ดํ„ฐ ์—†์„ ์‹œ "๋ฐ˜๋ คํ•œ ๊ฒฌ์ ์ด ์—†์Šต๋‹ˆ๋‹ค" ์•ˆ๋‚ด
  • ๊ฒฌ์  ๋ฐœ์†ก ๋ชจ๋‹ฌ

    • React Hook Form์œผ๋กœ ํผ ๊ด€๋ฆฌ
    • ๊ฒฌ์  ๊ฐ€๊ฒฉ: ์ˆซ์ž๋งŒ ์ž…๋ ฅ, 3์ž๋ฆฌ๋งˆ๋‹ค ์‰ผํ‘œ ์ž๋™ ํฌ๋งทํŒ…
    • ์ฝ”๋ฉ˜ํŠธ: ์ตœ์†Œ 10์ž~์ตœ๋Œ€ 30์ž, ์‹ค์‹œ๊ฐ„ ๊ธธ์ด ๊ฒ€์ฆ ๋ฐ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€
    • ์ด์‚ฌ ์ •๋ณด ํ‘œ์‹œ: ์ถœ๋ฐœ์ง€โ†’๋„์ฐฉ์ง€, ์ด์‚ฌ ๋‚ ์งœ, ์ง€์ • ๊ฒฌ์  ์—ฌ๋ถ€
    • ํผ ์œ ํšจ์„ฑ์— ๋”ฐ๋ผ ์ „์†ก ๋ฒ„ํŠผ ํ™œ์„ฑํ™”/๋น„ํ™œ์„ฑํ™”
  • ๊ฒฌ์  ๋ฐ˜๋ ค ๋ชจ๋‹ฌ

    • ๋ฐ˜๋ ค ์‚ฌ์œ  ์ž…๋ ฅ: ์ตœ์†Œ 10์ž~์ตœ๋Œ€ 30์ž ํ•„์ˆ˜ ์ž…๋ ฅ
    • ๊ฒฌ์  ๊ฐ€๊ฒฉ ์ž…๋ ฅ ํ•„๋“œ ์—†์Œ (๋ฐ˜๋ ค์ด๋ฏ€๋กœ)
    • ์‹ค์‹œ๊ฐ„ ์ž…๋ ฅ ๊ฒ€์ฆ ๋ฐ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ
  • ๊ฒฌ์  ์นด๋“œ ๊ณตํ†ต UI

    • ์ง€์ • ๊ฒฌ์  ํ‘œ์‹œ: "์ง€์ •" ๋ผ๋ฒจ ๊ฐ•์กฐ
    • ์ด์‚ฌ ํƒ€์ž… ๋ผ๋ฒจ: ์†Œํ˜•/๊ฐ€์ •/์‚ฌ๋ฌด์‹ค
    • ๋‚ ์งœ ํฌ๋งท: ๋กœ์ผ€์ผ๋ณ„ ์š”์ผ ํฌํ•จ (YYYY๋…„ MM์›” DD์ผ (์š”์ผ))
    • ์ƒํƒœ๋ณ„ ์•„์ด์ฝ˜: ํ™•์ • ์‹œ ์ฒดํฌ ์•„์ด์ฝ˜, ์ƒ‰์ƒ ๊ตฌ๋ถ„
5. ๋ฆฌ๋ทฐ
  • ์ž‘์„ฑ ๊ฐ€๋Šฅํ•œ ๋ฆฌ๋ทฐ

    • ์ด์‚ฌ ์™„๋ฃŒ ํ›„ 7์ผ ์ด๋‚ด ์ž‘์„ฑ ๊ฐ€๋Šฅํ•œ ๊ธฐ์‚ฌ๋‹˜ ๋ชฉ๋ก
    • ๊ธฐ์‚ฌ๋‹˜ ์ •๋ณด: ํ”„๋กœํ•„ ์ด๋ฏธ์ง€, ๋ณ„๋ช…, ํ•œ ์ค„ ์†Œ๊ฐœ, ์ด์‚ฌ ์ •๋ณด(์ถœ๋ฐœ์ง€/๋„์ฐฉ์ง€/๋‚ ์งœ), ์ตœ์ข… ๊ฒฌ์  ๊ฐ€๊ฒฉ
    • 4๊ฐœ์”ฉ ํŽ˜์ด์ง€๋„ค์ด์…˜, ๋ฐ์ดํ„ฐ ์—†์„ ์‹œ "์ž‘์„ฑ ๊ฐ€๋Šฅํ•œ ๋ฆฌ๋ทฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค" ์•ˆ๋‚ด + ์ผ๋Ÿฌ์ŠคํŠธ๋ ˆ์ด์…˜
    • URL ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ํŠน์ • ๋ฆฌ๋ทฐ ์ž‘์„ฑ ๋ชจ๋‹ฌ ์ž๋™ ์—ด๊ธฐ (?modal=write&reviewId=xxx)
  • ๋ฆฌ๋ทฐ ์ž‘์„ฑ ๋ชจ๋‹ฌ

    • React Hook Form + Controller ํŒจํ„ด์œผ๋กœ ํผ ๊ด€๋ฆฌ
    • ๋ณ„์  ์„ ํƒ: 1-5์ , ํด๋ฆญ์œผ๋กœ ์„ ํƒ, ํ•„์ˆ˜ ์ž…๋ ฅ ๊ฒ€์ฆ
    • ํ…์ŠคํŠธ ์ž…๋ ฅ: ์ตœ์†Œ 10์ž ์ด์ƒ, TextAreaInput ์ปดํฌ๋„ŒํŠธ ์‚ฌ์šฉ
    • ์ด์‚ฌ ์ •๋ณด ํ‘œ์‹œ: ๊ธฐ์‚ฌ๋‹˜ ํ”„๋กœํ•„, ์ถœ๋ฐœ์ง€โ†’๋„์ฐฉ์ง€, ์ด์‚ฌ ๋‚ ์งœ
    • ์ œ์ถœ ์ „ ํ™•์ธ ๋ชจ๋‹ฌ, ์ œ์ถœ ์ค‘ ๋กœ๋”ฉ ์ƒํƒœ ํ‘œ์‹œ
  • ์ž‘์„ฑ๋œ ๋ฆฌ๋ทฐ ๋ชฉ๋ก

    • ๋‚ด๊ฐ€ ์ž‘์„ฑํ•œ ๋ฆฌ๋ทฐ ๋‚ด์—ญ: ๊ธฐ์‚ฌ๋‹˜ ์ •๋ณด, ๋ณ„์ , ๋ฆฌ๋ทฐ ๋‚ด์šฉ, ์ž‘์„ฑ์ผ
    • 4๊ฐœ์”ฉ ํŽ˜์ด์ง€๋„ค์ด์…˜, ์นด๋“œ ํด๋ฆญ ์‹œ ๊ธฐ์‚ฌ๋‹˜ ์ƒ์„ธ ํŽ˜์ด์ง€๋กœ ์ด๋™
    • ๋ฐ์ดํ„ฐ ์—†์„ ์‹œ "์ž‘์„ฑ๋œ ๋ฆฌ๋ทฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค" ์•ˆ๋‚ด + ์ผ๋Ÿฌ์ŠคํŠธ๋ ˆ์ด์…˜
    • ๋ฐ˜์‘ํ˜• ์นด๋“œ ๋ ˆ์ด์•„์›ƒ: ๋ชจ๋ฐ”์ผ ์„ธ๋กœํ˜•, ๋ฐ์Šคํฌํ†ฑ ๊ฐ€๋กœํ˜•
  • UI ๊ณตํ†ต ์š”์†Œ

    • ๋ณ„์  ํ‘œ์‹œ: ํ™œ์„ฑ/๋น„ํ™œ์„ฑ ๋ณ„ ์•„์ด์ฝ˜์œผ๋กœ ์‹œ๊ฐํ™”
    • ๊ธฐ์‚ฌ๋‹˜ ํ”„๋กœํ•„: ๊ธฐ๋ณธ ์ด๋ฏธ์ง€ fallback ์ฒ˜๋ฆฌ
    • ์ง€์—ญ๋ช…: ํ•œ๊ตญ์–ด ์‹œ ์ถ•์•ฝ๋œ ์ง€์—ญ๋ช… ํ‘œ์‹œ
    • ๋‚ ์งœ ํฌ๋งท: ๋กœ์ผ€์ผ๋ณ„ ๋‚ ์งœ ํ˜•์‹ ์ ์šฉ
6. ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ ์‹œ์Šคํ…œ
  • SSE ๊ธฐ๋ฐ˜ ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ

    • event-source-polyfill ๊ธฐ๋ฐ˜ ๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜์„ฑ ๋ณด์žฅ
    • JWT ํ† ํฐ ๊ธฐ๋ฐ˜ ์ธ์ฆ๋œ SSE ์—ฐ๊ฒฐ
    • ๋„คํŠธ์›Œํฌ ์ƒํƒœ ๋ณ€ํ™” ๊ฐ์ง€ ๋ฐ ์ž๋™ ์žฌ์—ฐ๊ฒฐ (์˜จ๋ผ์ธ/์˜คํ”„๋ผ์ธ)
    • 5๋ถ„ ์ฃผ๊ธฐ ํ† ํฐ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๋ฐ ์—ฐ๊ฒฐ ์ƒํƒœ ๊ด€๋ฆฌ
  • ์•Œ๋ฆผ ์ƒํƒœ ๊ด€๋ฆฌ

    • Zustand ๊ธฐ๋ฐ˜ ์ „์—ญ ์•Œ๋ฆผ ์ƒํƒœ ๊ด€๋ฆฌ
    • ๋ฌดํ•œ์Šคํฌ๋กค ์•Œ๋ฆผ ๋ชฉ๋ก (4๊ฐœ์”ฉ ํŽ˜์ด์ง€๋„ค์ด์…˜)
    • react-intersection-observer ๊ธฐ๋ฐ˜ ์ž๋™ ๋กœ๋”ฉ
    • ์ฝ์Œ/์ฝ์ง€์•Š์Œ ์ƒํƒœ ์‹ค์‹œ๊ฐ„ ์—…๋ฐ์ดํŠธ
  • ์•Œ๋ฆผ UI/UX

    • DOMPurify HTML ์ •ํ™” + html-react-parser ์•ˆ์ „ํ•œ ๋ Œ๋”๋ง
    • ์ƒ๋Œ€์‹œ๊ฐ„ ํ‘œ์‹œ (formatRelativeTimeWithTranslations) + ๋‹ค๊ตญ์–ด ์ง€์›
    • ์ฝ์ง€์•Š์€ ์•Œ๋ฆผ ํŽ„์Šค ํšจ๊ณผ + ํด๋ฆญ ์‹œ ์ž๋™ ์ฝ์Œ ์ฒ˜๋ฆฌ
    • ์‹œ๋งจํ‹ฑ HTML (<article>, <time>) + ARIA ์ ‘๊ทผ์„ฑ
  • ์•Œ๋ฆผ ์œ ํ˜•

    • ์ผ๋ฐ˜ ์œ ์ €: ์ƒˆ ๊ฒฌ์  ๋„์ฐฉ / ๊ฒฌ์  ํ™•์ • / ์ด์‚ฌ ๋‹น์ผ ์•Œ๋ฆผ
    • ๊ธฐ์‚ฌ๋‹˜: ์ƒˆ ๊ฒฌ์  ์š”์ฒญ / ๊ฒฌ์  ํ™•์ • / ์ด์‚ฌ ๋‹น์ผ ์•Œ๋ฆผ
    • ํด๋ฆญ ์‹œ ํ•ด๋‹น ํŽ˜์ด์ง€๋กœ ์ž๋™ ์ด๋™ + ์ฝ์Œ ์ฒ˜๋ฆฌ
7. ๋‹ค๊ตญ์–ด ์‹œ์Šคํ…œ (i18n)
  • Next.js i18n ํ†ตํ•ฉ

    • next-intl ๊ธฐ๋ฐ˜ ์„œ๋ฒ„/ํด๋ผ์ด์–ธํŠธ ํ†ตํ•ฉ ๋‹ค๊ตญ์–ด ์ง€์›
    • 3๊ฐœ ์–ธ์–ด ์ง€์›: ํ•œ๊ตญ์–ด(ko), ์˜์–ด(en), ์ค‘๊ตญ์–ด(zh)
    • ๋™์  ๋ฉ”์‹œ์ง€ ๋กœ๋”ฉ (import('../messages/${locale}.json'))
    • SSR/CSR ๋ชจ๋‘์—์„œ ์ผ๊ด€๋œ ๋ฒˆ์—ญ ์ œ๊ณต
  • ๋ผ์šฐํŒ… ๋ฐ ๋ฏธ๋“ค์›จ์–ด

    • ๋กœ์ผ€์ผ ๊ธฐ๋ฐ˜ ๋™์  ๋ผ์šฐํŒ… (/ko/, /en/, /zh/)
    • Next.js ๋ฏธ๋“ค์›จ์–ด์—์„œ ๋กœ์ผ€์ผ ์ž๋™ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ
    • ์‚ฌ์šฉ์ž ์–ธ์–ด ์„ ํ˜ธ๋„ ์ฟ ํ‚ค ๊ธฐ๋ฐ˜ ์ž๋™ ์–ธ์–ด ์„ค์ •
    • ์œ ํšจํ•˜์ง€ ์•Š์€ ๋กœ์ผ€์ผ ์ ‘๊ทผ ์‹œ 404 ์ฒ˜๋ฆฌ
  • ์–ธ์–ด ์„ค์ • ๊ด€๋ฆฌ

    • ์ฟ ํ‚ค + ๋กœ์ปฌ์Šคํ† ๋ฆฌ์ง€ ์ด์ค‘ ์ €์žฅ์œผ๋กœ ์•ˆ์ •์„ฑ ๋ณด์žฅ
    • ๋ธŒ๋ผ์šฐ์ € ์–ธ์–ด ์ž๋™ ๊ฐ์ง€ (navigator.language)
    • ์–ธ์–ด ๋ณ€๊ฒฝ ์‹œ ์‹ค์‹œ๊ฐ„ ํŽ˜์ด์ง€ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ
    • ์„ค์ • ๋™๊ธฐํ™” ๋ฐ ์ดˆ๊ธฐํ™” ์‹œ์Šคํ…œ
  • LanguageSwitcher UI

    • ๋“œ๋กญ๋‹ค์šด ํ˜•ํƒœ์˜ ์–ธ์–ด ์„ ํƒ๊ธฐ
    • ํ‚ค๋ณด๋“œ ์ ‘๊ทผ์„ฑ ์ง€์› (ESC ํ‚ค๋กœ ๋‹ซ๊ธฐ)
    • ์™ธ๋ถ€ ํด๋ฆญ ๊ฐ์ง€๋กœ ์ž๋™ ๋‹ซ๊ธฐ
    • ํ˜„์žฌ ์–ธ์–ด ์ƒํƒœ ํ‘œ์‹œ + ์•„์ด์ฝ˜ ์• ๋‹ˆ๋ฉ”์ด์…˜
  • ๋‹ค๊ตญ์–ด ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ

    • ๋‚ ์งœ/์‹œ๊ฐ„ ๋กœ์ผ€์ผ๋ณ„ ํฌ๋งทํŒ… (formatDateWithDay, formatRelativeTime)
    • ์ˆซ์ž/ํ†ตํ™” ํ˜•์‹ ๋‹ค๊ตญ์–ด ์ฒ˜๋ฆฌ
    • ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ๋‹ค๊ตญ์–ด ์ง€์› (handleAuthErrorToast)
    • ๋ฒˆ์—ญ ํ‚ค ๊ฒ€์ฆ ๋ฐ fallback ์ฒ˜๋ฆฌ
  • ์„ฑ๋Šฅ ์ตœ์ ํ™”

    • ๋ฉ”์‹œ์ง€ ํŒŒ์ผ ์ง€์—ฐ ๋กœ๋”ฉ (ํ•„์š”ํ•œ ๋กœ์ผ€์ผ๋งŒ)
    • ๋ฒˆ์—ญ ์บ์‹ฑ ๋ฐ ๋ฉ”๋ชจ์ด์ œ์ด์…˜
    • ์„œ๋ฒ„/ํด๋ผ์ด์–ธํŠธ ํ•˜์ด๋“œ๋ ˆ์ด์…˜ ๋™๊ธฐํ™”
    • Sentry ํ†ตํ•ฉ ์—๋Ÿฌ ์ถ”์ 
8. ๋ณด์•ˆ ์‹œ์Šคํ…œ
  • ์ธ์ฆ ๋ฐ ๊ถŒํ•œ ๊ด€๋ฆฌ

    • JWT ํ† ํฐ ๊ธฐ๋ฐ˜ ์ธ์ฆ (Access Token + Refresh Token)
    • httpOnly, secure ์ฟ ํ‚ค๋กœ ํ† ํฐ ์ €์žฅ
    • 15๋ถ„ ํ† ํฐ ๋งŒ๋ฃŒ + 14๋ถ„ ์„ ์ œ ๊ฐฑ์‹ ์œผ๋กœ ๋ณด์•ˆ์„ฑ ๊ฐ•ํ™”
    • ๋ฏธ๋“ค์›จ์–ด ๊ธฐ๋ฐ˜ ๊ถŒํ•œ ๊ฒ€์ฆ (verifyAccessToken, optionalAuth)
  • ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณด์•ˆ

    • bcrypt ํ•ด์‹ฑ์œผ๋กœ ๋น„๋ฐ€๋ฒˆํ˜ธ ์•ˆ์ „ ์ €์žฅ
    • ์†”ํŠธ ๋ผ์šด๋“œ ์ ์šฉ์œผ๋กœ ๋ ˆ์ธ๋ณด์šฐ ํ…Œ์ด๋ธ” ๊ณต๊ฒฉ ๋ฐฉ์ง€
    • ํ˜„์žฌ ๋น„๋ฐ€๋ฒˆํ˜ธ ๊ฒ€์ฆ ํ›„ ๋ณ€๊ฒฝ ํ—ˆ์šฉ
  • ๋ฐ์ดํ„ฐ ์•”ํ˜ธํ™”

    • AES-256-CBC ๋ฐฉ์‹์œผ๋กœ ์ „ํ™”๋ฒˆํ˜ธ ๋“ฑ ๋ฏผ๊ฐ์ •๋ณด ์•”ํ˜ธํ™”
    • ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๊ธฐ๋ฐ˜ ์•”ํ˜ธํ™” ํ‚ค ๊ด€๋ฆฌ
    • ํŒŒ์ผ๋ช… ํƒ€์ž„์Šคํƒฌํ”„ + nanoid๋กœ ์—…๋กœ๋“œ ํŒŒ์ผ ๋ณด์•ˆ
  • ๋„คํŠธ์›Œํฌ ๋ณด์•ˆ

    • HTTPS ๊ฐ•์ œ ์ ์šฉ
    • CORS ์ •์ฑ…์œผ๋กœ ํ—ˆ์šฉ๋œ ๋„๋ฉ”์ธ๋งŒ ์ ‘๊ทผ
    • Rate Limiting์œผ๋กœ ๋ฌด์ฐจ๋ณ„ ๋Œ€์ž… ๊ณต๊ฒฉ ๋ฐฉ์ง€
    • Helmet.js ๋ณด์•ˆ ํ—ค๋” ์„ค์ •
  • ์ž…๋ ฅ ๊ฒ€์ฆ ๋ฐ XSS ๋ฐฉ์ง€

    • ๋ชจ๋“  ์‚ฌ์šฉ์ž ์ž…๋ ฅ ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ ๋ฐ ์ •์ œ
    • DOMPurify๋กœ HTML ์ฝ˜ํ…์ธ  ์ •ํ™”
    • html-react-parser๋กœ ์•ˆ์ „ํ•œ HTML ๋ Œ๋”๋ง
    • SQL ์ธ์ ์…˜ ๋ฐฉ์ง€ (Prisma ORM ์‚ฌ์šฉ)
  • ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋ณด์•ˆ

    • ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ ์ƒ์„ธ ์—๋Ÿฌ ์ •๋ณด ๋…ธ์ถœ ์ฐจ๋‹จ
    • Sentry ๊ธฐ๋ฐ˜ ์—๋Ÿฌ ๋ชจ๋‹ˆํ„ฐ๋ง ๋ฐ ๋กœ๊น…
    • ์‚ฌ์šฉ์ž์—๊ฒŒ๋Š” ์ผ๋ฐ˜ํ™”๋œ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋งŒ ํ‘œ์‹œ
  • ์„ธ์…˜ ๊ด€๋ฆฌ

    • ๋กœ๊ทธ์•„์›ƒ ์‹œ ํ† ํฐ ๋ฌดํšจํ™”
    • ๋™์‹œ ๋กœ๊ทธ์ธ ์„ธ์…˜ ๊ด€๋ฆฌ
    • ๋น„ํ™œ์„ฑ ์„ธ์…˜ ์ž๋™ ๋งŒ๋ฃŒ ์ฒ˜๋ฆฌ
9. ์„œ๋น„์Šค ์•ˆ์ •ํ™”
  • ๋ชจ๋‹ˆํ„ฐ๋ง ๋ฐ ์—๋Ÿฌ ์ถ”์ 

    • Sentry ํ†ตํ•ฉ์œผ๋กœ ์‹ค์‹œ๊ฐ„ ์—๋Ÿฌ ๋ชจ๋‹ˆํ„ฐ๋ง
    • ์—๋Ÿฌ ์ปจํ…์ŠคํŠธ ์„ค์ • (์‚ฌ์šฉ์ž ์ •๋ณด, ์•ก์…˜ ๋กœ๊ทธ)
    • ์„ฑ๋Šฅ ๋ฉ”ํŠธ๋ฆญ ์ถ”์  ๋ฐ ์•Œ๋ฆผ ์„ค์ •
    • ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ ์ฆ‰์‹œ ํŒ€ ์•Œ๋ฆผ
  • CDN ๋ฐ ์ •์  ์ž์‚ฐ ์ตœ์ ํ™”

    • CloudFront ๊ธฐ๋ฐ˜ ์ „ ์„ธ๊ณ„ ์ •์  ๋ฆฌ์†Œ์Šค ๋ฐฐํฌ
    • ์ด๋ฏธ์ง€, ํฐํŠธ, CSS/JS ํŒŒ์ผ ์บ์‹ฑ ์ตœ์ ํ™”
    • ์••์ถ• ๋ฐ ์ตœ์ ํ™”๋œ ๋ฆฌ์†Œ์Šค ์ „์†ก
    • ์ง€์—ญ๋ณ„ ์—ฃ์ง€ ์„œ๋ฒ„ ํ™œ์šฉ
  • ํ…Œ์ŠคํŠธ ๋ฐ ํ’ˆ์งˆ ๊ด€๋ฆฌ

    • Jest ๊ธฐ๋ฐ˜ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ๋ฐ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ
    • ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€ ์ธก์ • ๋ฐ ๋ชฉํ‘œ ๋‹ฌ์„ฑ
    • E2E ํ…Œ์ŠคํŠธ๋กœ ์‚ฌ์šฉ์ž ์‹œ๋‚˜๋ฆฌ์˜ค ๊ฒ€์ฆ
    • CI/CD ํŒŒ์ดํ”„๋ผ์ธ์—์„œ ์ž๋™ ํ…Œ์ŠคํŠธ ์‹คํ–‰
  • ๋กœ๊น… ๋ฐ ๋””๋ฒ„๊น…

    • ๊ตฌ์กฐํ™”๋œ ๋กœ๊น… ์‹œ์Šคํ…œ ๊ตฌ์ถ•
    • ์‚ฌ์šฉ์ž ์•ก์…˜ ์ถ”์  ๋ฐ ๋ถ„์„
    • ๊ฐœ๋ฐœ/ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ๋ณ„ ๋กœ๊ทธ ๋ ˆ๋ฒจ ์„ค์ •
    • ๋กœ๊ทธ ๋ณด๊ด€ ๋ฐ ๊ฒ€์ƒ‰ ์‹œ์Šคํ…œ
  • ๋ฐฑ์—… ๋ฐ ๋ณต๊ตฌ

    • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ •๊ธฐ ๋ฐฑ์—… ์Šค์ผ€์ค„๋ง
    • S3 ๋ฒ„ํ‚ท ๋ฐ์ดํ„ฐ ๋ฐฑ์—… ๋ฐ ๋ฒ„์ „ ๊ด€๋ฆฌ
    • ์žฅ์•  ๋ฐœ์ƒ ์‹œ ๋น ๋ฅธ ๋ณต๊ตฌ ํ”„๋กœ์„ธ์Šค
    • ์žฌํ•ด ๋ณต๊ตฌ ๊ณ„ํš ๋ฐ ํ…Œ์ŠคํŠธ
10. ์›น ์ ‘๊ทผ์„ฑ (Web Accessibility)
  • ์‹œ๋งจํ‹ฑ HTML ๊ตฌ์กฐ

    • <header>, <nav>, <main>, <section>, <article> ๋“ฑ ์˜๋ฏธ์žˆ๋Š” ํƒœ๊ทธ ์‚ฌ์šฉ
    • ์ œ๋ชฉ ๊ณ„์ธต ๊ตฌ์กฐ (<h1> ~ <h6>)๋กœ ์ฝ˜ํ…์ธ  ๊ตฌ์กฐํ™”
    • <button>, <input>, <label> ๋“ฑ ์ ์ ˆํ•œ ํผ ์š”์†Œ ์‚ฌ์šฉ
    • <table> ๊ตฌ์กฐํ™” ๋ฐ <caption> ์ œ๊ณต
  • WAI-ARIA ์†์„ฑ ๋ฐ ์—ญํ• 

    • aria-label, aria-describedby๋กœ ์š”์†Œ ์„ค๋ช… ์ œ๊ณต
    • role ์†์„ฑ์œผ๋กœ ์š”์†Œ ์—ญํ•  ๋ช…์‹œ (button, navigation, main ๋“ฑ)
    • aria-expanded, aria-hidden ๋“ฑ ์ƒํƒœ ์ •๋ณด ์ „๋‹ฌ
    • aria-live๋กœ ๋™์  ์ฝ˜ํ…์ธ  ๋ณ€๊ฒฝ ์•Œ๋ฆผ
  • ํ‚ค๋ณด๋“œ ์ ‘๊ทผ์„ฑ

    • ๋ชจ๋“  ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ์š”์†Œ์— ํ‚ค๋ณด๋“œ ํฌ์ปค์Šค ์ง€์›
    • Tab ์ˆœ์„œ ๋…ผ๋ฆฌ์  ๊ตฌ์„ฑ ๋ฐ ์‹œ๊ฐ์  ํฌ์ปค์Šค ํ‘œ์‹œ
    • Enter, Space, ํ™”์‚ดํ‘œ ํ‚ค ๋“ฑ ํ‚ค๋ณด๋“œ ๋‹จ์ถ•ํ‚ค ์ง€์›
    • ESC ํ‚ค๋กœ ๋ชจ๋‹ฌ/๋“œ๋กญ๋‹ค์šด ๋‹ซ๊ธฐ ๊ธฐ๋Šฅ
  • ์Šคํฌ๋ฆฐ ๋ฆฌ๋” ์ง€์›

    • sr-only ํด๋ž˜์Šค๋กœ ์ˆจ๊ฒจ์ง„ ์„ค๋ช… ํ…์ŠคํŠธ ์ œ๊ณต
    • ์ด๋ฏธ์ง€์— ์˜๋ฏธ์žˆ๋Š” alt ํ…์ŠคํŠธ ์„ค์ •
    • ํผ ์š”์†Œ์— ์ ์ ˆํ•œ label ์—ฐ๊ฒฐ
    • ์—๋Ÿฌ ๋ฉ”์‹œ์ง€์™€ ๋„์›€๋ง ํ…์ŠคํŠธ ์ œ๊ณต
  • ์ƒ‰์ƒ ๋ฐ ๋Œ€๋น„

    • WCAG AA ๊ธฐ์ค€ ์ถฉ์กฑํ•˜๋Š” ์ƒ‰์ƒ ๋Œ€๋น„ ๋น„์œจ
    • ์ƒ‰์ƒ๋งŒ์œผ๋กœ ์ •๋ณด ์ „๋‹ฌํ•˜์ง€ ์•Š์Œ
    • ๊ณ ๋Œ€๋น„ ๋ชจ๋“œ ์ง€์› ๋ฐ ํ…Œ์ŠคํŠธ
    • ํฌ์ปค์Šค ์ƒํƒœ ๋ช…ํ™•ํ•œ ์‹œ๊ฐ์  ํ‘œ์‹œ
  • ๋ฐ˜์‘ํ˜• ๋ฐ ๋ชจ๋ฐ”์ผ ์ ‘๊ทผ์„ฑ

    • ํ„ฐ์น˜ ํƒ€๊ฒŸ ํฌ๊ธฐ ์ตœ์†Œ 44x44px ๋ณด์žฅ
    • ๋ชจ๋ฐ”์ผ์—์„œ๋„ ํ‚ค๋ณด๋“œ ์ ‘๊ทผ์„ฑ ์œ ์ง€
    • ๋‹ค์–‘ํ•œ ํ™”๋ฉด ํฌ๊ธฐ์—์„œ ์ ‘๊ทผ์„ฑ ๋ณด์žฅ
    • ํ„ฐ์น˜ ์ œ์Šค์ฒ˜์™€ ํ‚ค๋ณด๋“œ ์ž…๋ ฅ ๋ชจ๋‘ ์ง€์›

๐Ÿš€ ์„ฑ๋Šฅ ์ตœ์ ํ™” ์ „๋žต

1. ๋ฐฑ์—”๋“œ ์บ์‹ฑ ์‹œ์Šคํ…œ
  • Redis ๊ธฐ๋ฐ˜ API ์บ์‹ฑ
    • GET ์š”์ฒญ๋งŒ ์บ์‹ฑ, 60์ดˆ TTL ์„ค์ •
    • ์‚ฌ์šฉ์ž๋ณ„ ๋ถ„๊ธฐ(varyByAuth) ์ง€์›
    • ์บ์‹œ ํ‚ค ๊ทœ์น™ ๋ฐ X-Cache-Status ํ—ค๋” ์ œ๊ณต
    • ๊ด€๋ จ ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ ์‹œ ์ž๋™ ์บ์‹œ ๋ฌดํšจํ™”
2. ์ด๋ฏธ์ง€ ์ตœ์ ํ™” & CDN
  • S3 + CloudFront ์ด๋ฏธ์ง€ ํ˜ธ์ŠคํŒ…

    • Presigned URL ์ง์ ‘ ์—…๋กœ๋“œ๋กœ ์„œ๋ฒ„ ๋ถ€ํ•˜ ๊ฐ์†Œ
    • CloudFront URL ์ƒ์„ฑ์œผ๋กœ ์ „ ์„ธ๊ณ„ ์บ์‹ฑ
    • WebP ํ˜•์‹ ์ž๋™ ๋ณ€ํ™˜ + ์••์ถ• ์ตœ์ ํ™”
  • ์šฐ์„ ์ˆœ์œ„ ๊ธฐ๋ฐ˜ ๋กœ๋”ฉ

    • Priority ์†์„ฑ ์ ์šฉ์œผ๋กœ ์ค‘์š” ์ด๋ฏธ์ง€ ์šฐ์„  ๋กœ๋”ฉ
    • Above-the-fold ์ด๋ฏธ์ง€ ์ฆ‰์‹œ ๋กœ๋”ฉ
    • next/image ์ตœ์ ํ™” ์ ์šฉ
3. LCP ์ตœ์ ํ™”
  • ์Šค์ผˆ๋ ˆํ†ค UI ์ ์šฉ

    • MovingTruckLoader ์Šค์ผˆ๋ ˆํ†ค์œผ๋กœ ์ฒด๊ฐ ์†๋„ ํ–ฅ์ƒ
    • ์ค‘์š” ์ปจํ…์ธ  ์šฐ์„  ๋ Œ๋”๋ง + ์ ์ง„์  ๋กœ๋”ฉ
    • ๋กœ๋”ฉ ์ƒํƒœ ์‹œ๊ฐ์  ํ”ผ๋“œ๋ฐฑ ์ œ๊ณต
  • ์ด๋ฏธ์ง€ ์šฐ์„ ์ˆœ์œ„ ์ตœ์ ํ™”

    • priority ์†์„ฑ์œผ๋กœ ํ•ต์‹ฌ ์ด๋ฏธ์ง€ ์šฐ์„  ์ฒ˜๋ฆฌ
    • ์ง€์—ฐ ๋กœ๋”ฉ์œผ๋กœ ๋ถˆํ•„์š”ํ•œ ๋ฆฌ์†Œ์Šค ์ œ๊ฑฐ
4. ๋ฒˆ๋“ค ํฌ๊ธฐ ์ตœ์ ํ™”
  • ์ฝ”๋“œ ์ตœ์ ํ™”

    • ๋ถˆํ•„์š”ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ œ๊ฑฐ ๋ฐ ํŠธ๋ฆฌ ์‰์ดํ‚น
    • ์ค‘๋ณต ์ฝ”๋“œ ์ œ๊ฑฐ ๋ฐ ๊ณตํ†ต ์ปดํฌ๋„ŒํŠธํ™”
    • Dynamic import๋กœ ์ฝ”๋“œ ์Šคํ”Œ๋ฆฌํŒ… (next/dynamic)
  • ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ตœ์ ํ™”

    • ํ•„์š”ํ•œ ์‹œ์ ์—๋งŒ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋กœ๋“œ (Daum Postcode ๋“ฑ)
    • ๊ฒฝ๋Ÿ‰ ๋Œ€์•ˆ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ ํƒ
    • ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ธฐ๋Šฅ ์ œ๊ฑฐ
5. React Query ์ตœ์ ํ™”
  • ์บ์‹ฑ ์ „๋žต

    • staleTime, gcTime ์„ค์ •์œผ๋กœ ๋ถˆํ•„์š”ํ•œ ๋ฆฌํŒจ์น˜ ๋ฐฉ์ง€
    • ๋ฌดํ•œ์Šคํฌ๋กค placeholderData ์‚ฌ์šฉ์œผ๋กœ ๊นœ๋นก์ž„ ๋ฐฉ์ง€
    • ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ (Promise.all) + ํšจ์œจ์ ์ธ ์บ์‹œ ๋ฌดํšจํ™”
  • ์˜ตํ‹ฐ๋ฏธ์Šคํ‹ฑ ์—…๋ฐ์ดํŠธ

    • ์ฆ‰๊ฐ์ ์ธ UI ๋ฐ˜์‘์œผ๋กœ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ํ–ฅ์ƒ
    • ์‹คํŒจ ์‹œ ์ž๋™ ๋กค๋ฐฑ์œผ๋กœ ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ ๋ณด์žฅ
6. ์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ์ตœ์ ํ™”
  • ๋กœ๋”ฉ ์ƒํƒœ ์ฒ˜๋ฆฌ

    • ์Šค์ผˆ๋ ˆํ†ค, ์Šคํ”ผ๋„ˆ, ์ง„ํ–‰๋ฅ  ๋ฐ” ์ ์šฉ
    • ์—๋Ÿฌ ์ƒํƒœ UI + ์žฌ์‹œ๋„ ๋ฉ”์ปค๋‹ˆ์ฆ˜
    • ๋””๋ฐ”์šด์‹ฑ ๊ฒ€์ƒ‰์œผ๋กœ ๋ถˆํ•„์š”ํ•œ API ํ˜ธ์ถœ ๋ฐฉ์ง€
  • ๋ฉ”๋ชจ๋ฆฌ ๋ฐ ๋ Œ๋”๋ง ์ตœ์ ํ™”

    • useMemo, useCallback์œผ๋กœ ๋ถˆํ•„์š”ํ•œ ์žฌ๊ณ„์‚ฐ/์žฌ๋ Œ๋”๋ง ๋ฐฉ์ง€
    • react-intersection-observer๋กœ ํšจ์œจ์ ์ธ ๋ฌดํ•œ์Šคํฌ๋กค
    • ์กฐ๊ฑด๋ถ€ ๋ Œ๋”๋ง์œผ๋กœ ๋ถˆํ•„์š”ํ•œ ์ปดํฌ๋„ŒํŠธ ๋งˆ์šดํŠธ ๋ฐฉ์ง€

๐Ÿ’ฃ ํŠธ๋Ÿฌ๋ธ” ์ŠˆํŒ…

1. [๊ฐ„ํŽธ ๋กœ๊ทธ์ธ ์ „ํ™˜] ์ „ํ™˜ ๊ธฐ๋Šฅ์˜ UX ์˜ค๋ฅ˜์™€ ํ•ด๊ฒฐ ๋ฐฉ์•ˆ

โš ๏ธ ๋ฌธ์ œ ์ƒํ™ฉ

  • ๊ฐ„ํŽธ ๋กœ๊ทธ์ธ ์ „ํ™˜ ์‹œ: ์ผ๋ฐ˜ ํšŒ์› โ†” ์ด์‚ฌ ๊ธฐ์‚ฌ๋‹˜ ์—ญํ•  ์ „ํ™˜
  • ๋ฐœ์ƒ ํ˜„์ƒ: ํ”„๋กœํ•„ ๋“ฑ๋ก ํŽ˜์ด์ง€๊ฐ€ ์งง์€ ์ˆœ๊ฐ„ ๊นœ๋นก์ด๋Š” UX ์˜ค๋ฅ˜
  • ์˜ํ–ฅ ๋ฒ”์œ„: ์ด๋ฏธ ํ”„๋กœํ•„์ด ๋“ฑ๋ก๋œ ์œ ์ €๋„ ์ž˜๋ชป๋œ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ๋ฐœ์ƒ

๐Ÿ” ์›์ธ ๋ถ„์„

Next.js ๋ฏธ๋“ค์›จ์–ด์˜ ์ฟ ํ‚ค ์ฐธ์กฐ ํƒ€์ด๋ฐ ์ด์Šˆ

โœ… ์‚ฌ์šฉ์ž๊ฐ€ ์—ญํ•  ์ „ํ™˜์„ ์š”์ฒญํ•˜๋ฉด ์ƒˆ๋กœ์šด ์ฟ ํ‚ค๊ฐ€ ์ƒ์„ฑ๋˜์ง€๋งŒ

โŒ ๋ฏธ๋“ค์›จ์–ด๋Š” ์ƒˆ๋กœ์šด ์ฟ ํ‚ค๊ฐ€ ์ ์šฉ๋˜๊ธฐ ์ „์— ์ด์ „์— ์‚ญ์ œ๋œ ์ฟ ํ‚ค๋ฅผ ์ฐธ์กฐ

โŒ ๊ฒฐ๊ณผ์ ์œผ๋กœ ์ธ์ฆ ์ƒํƒœ๋ฅผ ์ž˜๋ชป ํŒ๋‹จํ•˜์—ฌ /profile ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ

๊ตฌ์ฒด์ ์ธ ๋ฌธ์ œ์ 

  • ๋ฏธ๋“ค์›จ์–ด์—์„œ ํ† ํฐ์˜ hasProfile ๊ฐ’์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ”„๋กœํ•„ ๋“ฑ๋ก ์—ฌ๋ถ€ ํŒ๋‹จ
  • ์ฟ ํ‚ค ๊ฐฑ์‹  ํƒ€์ด๋ฐ๊ณผ ๋ฏธ๋“ค์›จ์–ด ์‹คํ–‰ ํƒ€์ด๋ฐ์˜ ๋ถˆ์ผ์น˜
  • SSR ํ™˜๊ฒฝ์—์„œ์˜ ์ƒํƒœ ๋™๊ธฐํ™” ๋ฌธ์ œ

โœ… ํ•ด๊ฒฐ ๋ฐฉ์•ˆ

UX ์ธก๋ฉด์—์„œ์˜ ๋Œ€์‘ ๋ฐฉ์‹ ์„ ํƒ

  1. ๋ฏธ๋“ค์›จ์–ด ๋กœ์ง ์ˆ˜์ • ๋Œ€์‹  UX ๊ฐœ์„ 

    • ๋ฏธ๋“ค์›จ์–ด ์ˆ˜์ • ์‹œ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์‚ฌ์ด๋“œ ์ดํŽ™ํŠธ ์šฐ๋ ค
    • ์ „์ฒด ์ธ์ฆ ํ๋ฆ„์— ์˜ํ–ฅ์„ ์ค„ ์ˆ˜ ์žˆ๋Š” ์œ„ํ—˜์„ฑ ๊ณ ๋ ค
  2. ์ „ํ™˜ ๋ฒ„ํŠผ ํ™œ์„ฑํ™” ์กฐ๊ฑด ์ œํ•œ

    • ์–‘์ชฝ ๋ชจ๋‘ ํ”„๋กœํ•„ ๋“ฑ๋ก ์™„๋ฃŒํ•œ ๊ฒฝ์šฐ์—๋งŒ ์ „ํ™˜ ๋ฒ„ํŠผ ํ™œ์„ฑํ™”
    • ์ž˜๋ชป๋œ ํ๋ฆ„์„ ์‚ฌ์ „์— ์ฐจ๋‹จํ•˜์—ฌ UX ์•ˆ์ •์„ฑ ํ™•๋ณด
    • ํ•œ ์ชฝ ์—ญํ• ๋งŒ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ ๋ฐ˜๋Œ€์ชฝ ๊ณ„์ • ์‚ฌ์šฉ ๋ถˆํ•„์š”ํ•˜๋‹ค๋Š” ํŒ๋‹จ
  3. ํ† ์ŠคํŠธ ๋ชจ๋‹ฌ์„ ํ†ตํ•œ ์ฆ‰๊ฐ์  ํ”ผ๋“œ๋ฐฑ

    • ์ „ํ™˜ ์™„๋ฃŒ ์‹œ ์ฆ‰์‹œ ์™„๋ฃŒ ๋ฉ”์‹œ์ง€ ์ œ๊ณต
    • ์‚ฌ์šฉ์ž๊ฐ€ ํ˜„์žฌ ์ƒํƒœ๋ฅผ ๋ช…ํ™•ํžˆ ์ธ์ง€ํ•  ์ˆ˜ ์žˆ๋„๋ก UX ๊ฐœ์„ 
    • ํŽ˜์ด์ง€ ๊นœ๋นก์ž„ ํ˜„์ƒ ์ตœ์†Œํ™”

๐Ÿ“š ๋ฐฐ์šด ์ 

  • ์ฟ ํ‚ค ๊ธฐ๋ฐ˜ ์ธ์ฆ์˜ ํƒ€์ด๋ฐ ์ด์Šˆ: ๋ฏธ๋“ค์›จ์–ด์™€ ์ฟ ํ‚ค ๊ฐฑ์‹  ๊ฐ„์˜ ๋™๊ธฐํ™” ๋ฌธ์ œ
  • UX vs ๊ธฐ์ˆ ์  ํ•ด๊ฒฐ์˜ ํŠธ๋ ˆ์ด๋“œ์˜คํ”„: ์™„๋ฒฝํ•œ ๊ธฐ์ˆ ์  ํ•ด๊ฒฐ๋ณด๋‹ค ์•ˆ์ •์ ์ธ UX ์šฐ์„ 
  • ์‚ฌ์ „ ๋ฐฉ์ง€์˜ ์ค‘์š”์„ฑ: ๋ฌธ์ œ ๋ฐœ์ƒ ํ›„ ์ˆ˜์ •๋ณด๋‹ค ๋ฐœ์ƒ ๊ฐ€๋Šฅ์„ฑ์„ ์ฐจ๋‹จํ•˜๋Š” ์„ค๊ณ„
  • ์‚ฌ์šฉ์ž ์ค‘์‹ฌ ์‚ฌ๊ณ : ๊ธฐ์ˆ ์  ์™„๋ฒฝํ•จ๋ณด๋‹ค ์‹ค์ œ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ๊ฐœ์„ ์— ์ง‘์ค‘
2. [์•Œ๋ฆผ ์‹œ์Šคํ…œ] HTML ๊ธฐ๋ฐ˜ ์•Œ๋ฆผ ๋ฉ”์‹œ์ง€์˜ XSS ๊ณต๊ฒฉ ์œ„ํ—˜๊ณผ ํ•ด๊ฒฐ ๋ฐฉ์•ˆ

๐Ÿ”’ ๋ฌธ์ œ ์ƒํ™ฉ

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

โš ๏ธ ๋ณด์•ˆ ์œ„ํ—˜ ์š”์†Œ

  • <script> ํƒœ๊ทธ๋ฅผ ํ†ตํ•œ ์•…์„ฑ ์ฝ”๋“œ ์‹คํ–‰
  • onerror, onclick ๋“ฑ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ์‚ฝ์ž…
  • <iframe> ๋“ฑ ์™ธ๋ถ€ ๋ฆฌ์†Œ์Šค ๋กœ๋”ฉ
  • CSS ํ‘œํ˜„์‹์„ ํ†ตํ•œ ๋ฐ์ดํ„ฐ ํƒˆ์ทจ

๐Ÿ” ์›์ธ ๋ถ„์„

HTML ๋ฌธ์ž์—ด ์ง์ ‘ ๋ Œ๋”๋ง์˜ ๋ณด์•ˆ ์ทจ์•ฝ์ 

โœ… ๋ฐฑ์—”๋“œ์—์„œ HTML ํƒœ๊ทธ์™€ ์Šคํƒ€์ผ์ด ํฌํ•จ๋œ ๋ฉ”์‹œ์ง€ ์ƒ์„ฑ

โŒ ํ”„๋ก ํŠธ์—”๋“œ์—์„œ HTML์„ ๊ฒ€์ฆ ์—†์ด ์ง์ ‘ ๋ Œ๋”๋ง

โŒ ์•…์„ฑ ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ํฌํ•จ๋œ ๊ฒฝ์šฐ ์ฆ‰์‹œ ์‹คํ–‰๋˜์–ด XSS ๊ณต๊ฒฉ ๋ฐœ์ƒ

๊ตฌ์ฒด์ ์ธ ๋ฌธ์ œ์ 

  • HTML ์ฝ˜ํ…์ธ ์˜ ์‹ ๋ขฐ์„ฑ ๊ฒ€์ฆ ๋ถ€์žฌ
  • ์Šคํฌ๋ฆฝํŠธ ์‹คํ–‰ ๋ฐฉ์ง€ ๋ฉ”์ปค๋‹ˆ์ฆ˜ ์—†์Œ
  • ์‚ฌ์šฉ์ž ์ž…๋ ฅ ๋ฐ์ดํ„ฐ์˜ ์•ˆ์ „์„ฑ ๋ณด์žฅ ๋ถˆ๊ฐ€

โœ… ํ•ด๊ฒฐ ๋ฐฉ์•ˆ

์ด์ค‘ ๋ฐฉ์–ด ์ฒด๊ณ„ ๊ตฌ์ถ•

  1. DOMPurify๋ฅผ ํ†ตํ•œ HTML ์ƒˆ๋‹ˆํƒ€์ด์ง•

    • <script>, <iframe> ๋“ฑ ์œ„ํ—˜ ์š”์†Œ ์ž๋™ ์ œ๊ฑฐ
    • onerror, onclick ๋“ฑ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ์ฐจ๋‹จ
    • ํ—ˆ์šฉ๋œ HTML ํƒœ๊ทธ๋งŒ ํ™”์ดํŠธ๋ฆฌ์ŠคํŠธ ๋ฐฉ์‹์œผ๋กœ ํ†ต๊ณผ
    • XSS ๊ณต๊ฒฉ ๊ฒฝ๋กœ ์›์ฒœ ์ฐจ๋‹จ
  2. html-react-parser๋ฅผ ํ†ตํ•œ ์•ˆ์ „ํ•œ ๋ Œ๋”๋ง

    • ์ƒˆ๋‹ˆํƒ€์ด์ง•๋œ HTML์„ React ์ปดํฌ๋„ŒํŠธ๋กœ ๋ณ€ํ™˜
    • React์˜ ๋‚ด์žฅ ๋ณด์•ˆ ๋ฉ”์ปค๋‹ˆ์ฆ˜ ํ™œ์šฉ
    • ์•ˆ์ „ํ•œ DOM ์กฐ์ž‘ ๋ฐ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ
  3. ๊ตฌํ˜„ ์ฝ”๋“œ ์˜ˆ์‹œ

    // DOMPurify๋กœ HTML ์ƒˆ๋‹ˆํƒ€์ด์ง•
    const sanitizedHtml = DOMPurify.sanitize(notificationMessage, {
      ALLOWED_TAGS: ["b", "i", "em", "strong", "a", "br"],
      ALLOWED_ATTR: ["href", "target"],
    });
    
    // html-react-parser๋กœ ์•ˆ์ „ํ•œ ๋ Œ๋”๋ง
    const parsedContent = parse(sanitizedHtml);

๐ŸŽฏ ๊ฐœ์„  ํšจ๊ณผ

  • ๋ณด์•ˆ์„ฑ ๊ฐ•ํ™”: XSS ๊ณต๊ฒฉ ๊ฒฝ๋กœ ์™„์ „ ์ฐจ๋‹จ
  • ์‚ฌ์šฉ์ž ์‹ ๋ขฐ: ์•ˆ์ „ํ•˜๊ณ  ์•ˆ์ •์ ์ธ ์•Œ๋ฆผ ์‹œ์Šคํ…œ ์ œ๊ณต
  • ์„ฑ๋Šฅ ์œ ์ง€: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ถ€ํ•˜ ๊ฐ์†Œ ํšจ๊ณผ ์œ ์ง€
  • ์œ ์ง€๋ณด์ˆ˜์„ฑ: HTML ์Šคํƒ€์ผ๋ง ์šฉ์ด์„ฑ ํ™•๋ณด

๐Ÿ“š ๋ฐฐ์šด ์ 

  • ๋ณด์•ˆ ์šฐ์„  ์‚ฌ๊ณ : ํŽธ์˜์„ฑ๋ณด๋‹ค ๋ณด์•ˆ์„ฑ ์šฐ์„  ๊ณ ๋ ค
  • ์ด์ค‘ ๋ฐฉ์–ด ์ฒด๊ณ„: ๋‹จ์ผ ๋ณด์•ˆ ์กฐ์น˜๋ณด๋‹ค ๋‹ค์ธต ๋ณด์•ˆ ์„ค๊ณ„
  • ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํ™œ์šฉ: ๊ฒ€์ฆ๋œ ๋ณด์•ˆ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ ๊ทน ํ™œ์šฉ
  • ์‚ฌ์ „ ๊ฒ€์ฆ์˜ ์ค‘์š”์„ฑ: ๋ Œ๋”๋ง ์ „ ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ์˜ ํ•„์ˆ˜์„ฑ
3. [์ฃผ์†Œ ๊ฒ€์ƒ‰] ์นด์นด์˜ค ์ฃผ์†Œ ๊ฒ€์ƒ‰ ํŒ์—… ์ฐจ๋‹จ ๋ฌธ์ œ์™€ iframe ์ž„๋ฒ ๋“œ ๋ฐฉ์‹ ์ „ํ™˜

๐Ÿšซ ๋ฌธ์ œ ์ƒํ™ฉ

  • ๊ธฐ์กด ๋ฐฉ์‹: window.open ํ•จ์ˆ˜๋ฅผ ํ†ตํ•œ ํŒ์—…์ฐฝ ๋ฐฉ์‹ ์ฃผ์†Œ ๊ฒ€์ƒ‰
  • ๋ฐœ์ƒ ๋ฌธ์ œ: ์ตœ์‹  ๋ธŒ๋ผ์šฐ์ €์˜ ๊ธฐ๋ณธ ํŒ์—… ์ฐจ๋‹จ ๊ธฐ๋Šฅ์— ์˜ํ•ด ์ฃผ์†Œ ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ ์ฐจ๋‹จ
  • ์‚ฌ์šฉ์ž ๊ฒฝํ—˜: ์ฃผ์†Œ ๊ฒ€์ƒ‰์ด ๋ถˆ๊ฐ€๋Šฅํ•˜์—ฌ ์‹ฌ๊ฐํ•œ UX ์ €ํ•˜

โš ๏ธ ๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜์„ฑ ๋ฌธ์ œ

  • Chrome, Firefox, Safari ๋“ฑ ์ฃผ์š” ๋ธŒ๋ผ์šฐ์ €์˜ ํŒ์—… ์ฐจ๋‹จ ์ •์ฑ…
  • ์‚ฌ์šฉ์ž๊ฐ€ ํŒ์—… ํ—ˆ์šฉ ์„ค์ •์„ ๋ณ€๊ฒฝํ•ด์•ผ ํ•˜๋Š” ๋ฒˆ๊ฑฐ๋กœ์›€
  • ๋ชจ๋ฐ”์ผ ๋ธŒ๋ผ์šฐ์ €์—์„œ์˜ ์ถ”๊ฐ€์ ์ธ ์ œ์•ฝ์‚ฌํ•ญ

๐Ÿ” ์›์ธ ๋ถ„์„

ํŒ์—… ๊ธฐ๋ฐ˜ ์ฃผ์†Œ ๊ฒ€์ƒ‰์˜ ํ•œ๊ณ„

โœ… ์‚ฌ์šฉ์ž๊ฐ€ ์ฃผ์†Œ ๊ฒ€์ƒ‰ ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ window.open ํ˜ธ์ถœ

โŒ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ํŒ์—… ์ฐจ๋‹จ ์ •์ฑ…์œผ๋กœ ์ธํ•ด ์ƒˆ ์ฐฝ ์ƒ์„ฑ ์ฐจ๋‹จ

โŒ ๊ฒฐ๊ณผ์ ์œผ๋กœ ์ฃผ์†Œ ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ ์ž์ฒด๊ฐ€ ๋™์ž‘ํ•˜์ง€ ์•Š์Œ

๊ตฌ์ฒด์ ์ธ ๋ฌธ์ œ์ 

  • window.open ๊ธฐ๋ฐ˜ ํŒ์—… ๋ฐฉ์‹์˜ ๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜์„ฑ ํ•œ๊ณ„
  • ํŒ์—… ์ฐจ๋‹จ ์ •์ฑ…์— ๋”ฐ๋ฅธ ๊ธฐ๋Šฅ ๋™์ž‘ ๋ถˆ๊ฐ€
  • ์‚ฌ์šฉ์ž ์„ค์ • ๋ณ€๊ฒฝ ์—†์ด๋Š” ํ•ด๊ฒฐ ๋ถˆ๊ฐ€๋Šฅํ•œ ๊ตฌ์กฐ์  ๋ฌธ์ œ

โœ… ํ•ด๊ฒฐ ๋ฐฉ์•ˆ

iframe ์ž„๋ฒ ๋“œ ๋ฐฉ์‹์œผ๋กœ ์ „ํ™˜

  1. ์นด์นด์˜ค ์ฃผ์†Œ API embed ํ•จ์ˆ˜ ํ™œ์šฉ

    • ํŒ์—… ๋Œ€์‹  ํŽ˜์ด์ง€ ๋‚ด์— ์ง์ ‘ ์ฃผ์†Œ ๊ฒ€์ƒ‰์ฐฝ ์‚ฝ์ž…
    • daum.Postcode ๊ฐ์ฒด์˜ embed ๋ฉ”์†Œ๋“œ ์‚ฌ์šฉ
    • ์ง€์ •๋œ ์ปจํ…Œ์ด๋„ˆ ์˜์—ญ์— ์ฃผ์†Œ ๊ฒ€์ƒ‰์ฐฝ ๋ Œ๋”๋ง
  2. ๋™์  ์Šคํฌ๋ฆฝํŠธ ๋กœ๋”ฉ ๋ฐ ์•ˆ์ •์„ฑ ํ™•๋ณด

    • ํ•„์š”ํ•  ๋•Œ๋งŒ ์นด์นด์˜ค API ์Šคํฌ๋ฆฝํŠธ ๋™์  ๋กœ๋“œ
    • ์Šคํฌ๋ฆฝํŠธ ๋กœ๋“œ ์™„๋ฃŒ ํ›„ new daum.Postcode ๊ฐ์ฒด ์ƒ์„ฑ
    • embed ๋ฉ”์†Œ๋“œ๋กœ ์ปจํ…Œ์ด๋„ˆ์— ์ฃผ์†Œ ๊ฒ€์ƒ‰์ฐฝ ์‚ฝ์ž…
  3. ๊ตฌํ˜„ ์ฝ”๋“œ ์˜ˆ์‹œ

    // ์นด์นด์˜ค API ์Šคํฌ๋ฆฝํŠธ ๋™์  ๋กœ๋”ฉ
    const loadDaumPostcodeScript = (): Promise<void> => {
      return new Promise((resolve, reject) => {
        if (window.daum?.Postcode) {
          resolve();
          return;
        }
    
        const script = document.createElement("script");
        script.src = "//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js";
        script.onload = () => resolve();
        script.onerror = () => reject();
        document.head.appendChild(script);
      });
    };
    
    // Postcode ๊ฐ์ฒด ์ƒ์„ฑ ๋ฐ ์ž„๋ฒ ๋“œ
    const postcode = new daum.Postcode({
      width: "100%",
      height: EMBED_HEIGHT,
      oncomplete: (data) => {
        // ์ฃผ์†Œ ์„ ํƒ ์™„๋ฃŒ ์‹œ ์ฒ˜๋ฆฌ
        onComplete(data);
        setShowAddressSearch(false);
      },
    });
    
    postcode.embed(containerRef.current!);
    embeddedRef.current = true;
  4. ๋ฆฌ๋ Œ๋”๋ง ์•ˆ์ •์„ฑ ํ™•๋ณด

    • ํŽ˜์ด์ง€ ๋ฆฌ๋ Œ๋”๋ง ์‹œ iframe ์ƒํƒœ ํ™•์ธ
    • iframe์ด ์‚ฌ๋ผ์ง„ ๊ฒฝ์šฐ ์žฌ์‹œ๋„ ๋กœ์ง ๊ตฌํ˜„
    • ์•ˆ์ •์ ์ธ ์ฃผ์†Œ ๊ฒ€์ƒ‰ UX ์ œ๊ณต

๐ŸŽฏ ๊ฐœ์„  ํšจ๊ณผ

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

๐Ÿ“š ๋ฐฐ์šด ์ 

  • ๋ธŒ๋ผ์šฐ์ € ์ •์ฑ… ๋Œ€์‘: ํŒ์—… ์ฐจ๋‹จ ๋“ฑ ๋ธŒ๋ผ์šฐ์ € ์ œ์•ฝ์‚ฌํ•ญ ์‚ฌ์ „ ๊ณ ๋ ค
  • ๋Œ€์•ˆ ๊ธฐ์ˆ  ํ™œ์šฉ: window.open ๋Œ€์‹  iframe ์ž„๋ฒ ๋“œ ๋ฐฉ์‹ ๋„์ž…
  • ๋™์  ๋กœ๋”ฉ ์ตœ์ ํ™”: ํ•„์š” ์‹œ์ ์—๋งŒ ์™ธ๋ถ€ ์Šคํฌ๋ฆฝํŠธ ๋กœ๋“œ
  • ์•ˆ์ •์„ฑ ํ™•๋ณด: ๋ฆฌ๋ Œ๋”๋ง ์ƒํ™ฉ์—์„œ๋„ ์ผ๊ด€๋œ ๊ธฐ๋Šฅ ๋™์ž‘ ๋ณด์žฅ
4. [์„ฑ๋Šฅ ์ตœ์ ํ™”] GET API ์‘๋‹ต ์†๋„ ์ €ํ•˜ ๋ฌธ์ œ์™€ Redis ์บ์‹ฑ ์ „๋žต ๋„์ž…

๐ŸŒ ๋ฌธ์ œ ์ƒํ™ฉ

  • ๋ฐœ์ƒ ํ˜„์ƒ: ์ผ๋ถ€ GET API, ํŠนํžˆ ์ž์ฃผ ํ˜ธ์ถœ๋˜๋Š” API์—์„œ ์‘๋‹ต ์†๋„ ์ €ํ•˜
  • ๋ถ€์ž‘์šฉ: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ถ€ํ•˜ ์ฆ๊ฐ€ ๋ฐ ์ „์ฒด ์‹œ์Šคํ…œ ์„ฑ๋Šฅ ์ €ํ•˜
  • ์˜ํ–ฅ ๋ฒ”์œ„: ์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ์ €ํ•˜ ๋ฐ ์„œ๋ฒ„ ๋ฆฌ์†Œ์Šค ๋‚ญ๋น„

โš ๏ธ ์„ฑ๋Šฅ ๋ฌธ์ œ์˜ ๊ตฌ์ฒด์  ์ฆ์ƒ

  • API ์‘๋‹ต ์‹œ๊ฐ„ ์ฆ๊ฐ€ (๋ช‡ ์ดˆ โ†’ ์ˆ˜์‹ญ ์ดˆ)
  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ํ’€ ๊ณ ๊ฐˆ
  • ๋™์‹œ ์‚ฌ์šฉ์ž ์ฆ๊ฐ€ ์‹œ ์„ฑ๋Šฅ ๊ธ‰๊ฒฉํ•œ ์ €ํ•˜

๐Ÿ” ์›์ธ ๋ถ„์„

๋ฐ˜๋ณต์ ์ธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ ‘๊ทผ์˜ ๋น„ํšจ์œจ์„ฑ

โœ… ๋™์ผํ•œ ์š”์ฒญ์ด ๋ฐ˜๋ณต์ ์œผ๋กœ ๋“ค์–ด์˜ฌ ๋•Œ๋งˆ๋‹ค

โŒ ๋งค๋ฒˆ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ ‘๊ทผํ•˜์—ฌ ๋ฐ์ดํ„ฐ ์กฐํšŒ

โŒ ๋ถˆํ•„์š”ํ•œ ๋ฆฌ์†Œ์Šค ๋‚ญ๋น„๋กœ ์ด์–ด์ง

๊ตฌ์ฒด์ ์ธ ๋ฌธ์ œ์ 

  • ๋™์ผํ•œ ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•œ ์ค‘๋ณต ์ฟผ๋ฆฌ ์‹คํ–‰
  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ๋ฐ ์ฟผ๋ฆฌ ์ฒ˜๋ฆฌ ์˜ค๋ฒ„ํ—ค๋“œ
  • ๋ฉ”๋ชจ๋ฆฌ ๋ฐ CPU ๋ฆฌ์†Œ์Šค ๋‚ญ๋น„

โœ… ํ•ด๊ฒฐ ๋ฐฉ์•ˆ

Redis ๊ธฐ๋ฐ˜ ์บ์‹ฑ ์ „๋žต ๋„์ž…

  1. Express ๋ฏธ๋“ค์›จ์–ด ํ˜•ํƒœ์˜ ์บ์‹œ ๋กœ์ง ๊ตฌํ˜„

    • Redis๋ฅผ ์บ์‹œ ์ €์žฅ์†Œ๋กœ ํ™œ์šฉ
    • cache ๋ฏธ๋“ค์›จ์–ด๋ฅผ ํŠน์ • ๋ผ์šฐํ„ฐ์— ์ถ”๊ฐ€
    • ttlSeconds ์˜ต์…˜์œผ๋กœ ์บ์‹œ ์œ ํšจ ๊ธฐ๊ฐ„ ์„ค์ •
  2. ์‚ฌ์šฉ์ž๋ณ„ ์บ์‹œ ํ‚ค ๋ถ„๋ฆฌ

    • varyByAuth: true ์˜ต์…˜ ์ ์šฉ
    • ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž ์—ฌ๋ถ€์— ๋”ฐ๋ผ ์บ์‹œ ํ‚ค ๋ถ„๋ฆฌ
    • ์‚ฌ์šฉ์ž๋ณ„๋กœ ๋‹ค๋ฅธ ๋‚ด์šฉ์ด ์บ์‹ฑ๋˜๋„๋ก ๋ณด์žฅ
  3. ๊ตฌํ˜„ ์ฝ”๋“œ ์˜ˆ์‹œ

    // estimateRequest.routes
    router.get(
      "/active",
      verifyAccessToken,
      cache({ ttlSeconds: 60, varyByAuth: true }),
      defaultTranslationMiddleware,
      (req, res) => estimateRequestController.getActiveEstimateRequest(req, res),
    );
  4. ์บ์‹œ ๋ฌดํšจํ™” ๋ฐ ๋ชจ๋‹ˆํ„ฐ๋ง

    • ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ ์‹œ ํŒจํ„ด ๊ธฐ๋ฐ˜ ์บ์‹œ ์‚ญ์ œ
    • POST, PUT, DELETE ์š”์ฒญ ๋ฐœ์ƒ ์‹œ ๊ด€๋ จ ์บ์‹œ ์ฆ‰์‹œ ๋ฌดํšจํ™”
    • X-Cache: HIT|MISS ํ—ค๋”๋กœ ์บ์‹œ ๋™์ž‘ ์—ฌ๋ถ€ ๋ชจ๋‹ˆํ„ฐ๋ง

๐ŸŽฏ ๊ฐœ์„  ํšจ๊ณผ

  • ์‘๋‹ต ์†๋„ ํ–ฅ์ƒ: ์บ์‹œ ํžˆํŠธ ์‹œ ์ฆ‰์‹œ ์‘๋‹ต (DB ์กฐํšŒ ์‹œ๊ฐ„ ๋‹จ์ถ•)
  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ถ€ํ•˜ ๊ฐ์†Œ: ์ค‘๋ณต ์ฟผ๋ฆฌ ์ œ๊ฑฐ๋กœ DB ์—ฐ๊ฒฐ ํ’€ ํšจ์œจ์„ฑ ์ฆ๋Œ€
  • ์‹œ์Šคํ…œ ์•ˆ์ •์„ฑ: ๋™์‹œ ์‚ฌ์šฉ์ž ์ฆ๊ฐ€ ์‹œ์—๋„ ์ผ์ •ํ•œ ์„ฑ๋Šฅ ์œ ์ง€
  • ์‚ฌ์šฉ์ž ๊ฒฝํ—˜: ๋น ๋ฅธ API ์‘๋‹ต์œผ๋กœ ์ „๋ฐ˜์ ์ธ UX ํ–ฅ์ƒ

๐Ÿ“š ๋ฐฐ์šด ์ 

  • ์บ์‹ฑ ์ „๋žต์˜ ์ค‘์š”์„ฑ: ๋ฐ˜๋ณต ์š”์ฒญ์— ๋Œ€ํ•œ ํšจ์œจ์ ์ธ ๋ฐ์ดํ„ฐ ์ œ๊ณต
  • ๋ฏธ๋“ค์›จ์–ด ํ™œ์šฉ: Express ๋ฏธ๋“ค์›จ์–ด๋กœ ์บ์‹ฑ ๋กœ์ง ์ฒด๊ณ„์  ๊ตฌํ˜„
  • ์‚ฌ์šฉ์ž๋ณ„ ๋ฐ์ดํ„ฐ ๋ถ„๋ฆฌ: varyByAuth ์˜ต์…˜์œผ๋กœ ๋ณด์•ˆ์„ฑ๊ณผ ์„ฑ๋Šฅ ๋™์‹œ ํ™•๋ณด
  • ์บ์‹œ ๋ฌดํšจํ™”์˜ ์ค‘์š”์„ฑ: ๋ฐ์ดํ„ฐ ์ตœ์‹ ์„ฑ ๋ณด์žฅ์„ ์œ„ํ•œ ์ ์ ˆํ•œ ๋ฌดํšจํ™” ์ „๋žต
  • ์„ฑ๋Šฅ ๋ชจ๋‹ˆํ„ฐ๋ง: X-Cache ํ—ค๋”๋ฅผ ํ†ตํ•œ ์บ์‹œ ๋™์ž‘ ์ƒํƒœ ์‹ค์‹œ๊ฐ„ ํ™•์ธ
5. [์ƒํƒœ ๊ด€๋ฆฌ] ๊ธฐ์‚ฌ๋‹˜ ์ฐพ๊ธฐ ํŽ˜์ด์ง€์˜ ๋ณต์žกํ•œ ์ƒํƒœ ๊ด€๋ฆฌ ๋ฌธ์ œ์™€ Zustand ๋„์ž…

๐Ÿ”„ ๋ฌธ์ œ ์ƒํ™ฉ

  • ๊ธฐ์‚ฌ๋‹˜ ์ฐพ๊ธฐ ํŽ˜์ด์ง€์—์„œ ์ง€์—ญ, ์„œ๋น„์Šค ํ•„ํ„ฐ, ์ •๋ ฌ, ๊ฒ€์ƒ‰์–ด ๋“ฑ ๋‹ค์–‘ํ•œ ์ƒํƒœ๋ฅผ ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ์—์„œ ๊ณต์œ 
  • ์ƒํƒœ ์˜์กด์„ฑ: ๊ฒ€์ƒ‰๋ฐ”, ํ•„ํ„ฐ๋ฐ”, ์ •๋ ฌ๋ฐ” ๋“ฑ ๊ฐ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์„œ๋กœ์˜ ์ƒํƒœ์— ์˜์กด
  • Props Drilling: ์ƒํƒœ๋ฅผ props๋กœ ๊ณ„์† ์ „๋‹ฌํ•ด์•ผ ํ•ด์„œ ์ฝ”๋“œ ๋ณต์žก์„ฑ ์ฆ๊ฐ€
  • ์œ ์ง€๋ณด์ˆ˜ ์–ด๋ ค์›€: ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งŽ์•„์งˆ์ˆ˜๋ก ์ƒํƒœ ์ „๋‹ฌ์ด ๋ณต์žกํ•ด์ง€๊ณ  ๋ฒ„๊ทธ ๋ฐœ์ƒ ์œ„ํ—˜

โš ๏ธ ์ƒํƒœ ๊ด€๋ฆฌ์˜ ๊ตฌ์ฒด์  ๋ฌธ์ œ์ 

  • ๊ฒ€์ƒ‰์–ด, ์ง€์—ญ ํ•„ํ„ฐ, ์„œ๋น„์Šค ํƒ€์ž…, ์ •๋ ฌ ๊ธฐ์ค€ ๋“ฑ ๋ณต์žกํ•œ ์ƒํƒœ ๊ตฌ์กฐ
  • ์ปดํฌ๋„ŒํŠธ ๊ฐ„ ์ƒํƒœ ๋™๊ธฐํ™”์˜ ์–ด๋ ค์›€
  • ์ƒํƒœ ๋ณ€๊ฒฝ ์‹œ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์‚ฌ์ด๋“œ ์ดํŽ™ํŠธ ๋ฐœ์ƒ

๐Ÿ” ์›์ธ ๋ถ„์„

๊ธฐ์กด ์ƒํƒœ ๊ด€๋ฆฌ ๋ฐฉ์‹์˜ ํ•œ๊ณ„

โœ… useState, useContext, props drilling ๋“ฑ์œผ๋กœ ์ƒํƒœ ๊ด€๋ฆฌ

โŒ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งŽ์•„์งˆ์ˆ˜๋ก ์ƒํƒœ ์ „๋‹ฌ์ด ๋ณต์žกํ•ด์ง

โŒ ์œ ์ง€๋ณด์ˆ˜/ํ™•์žฅ ์‹œ ๋ฒ„๊ทธ๊ฐ€ ๋ฐœ์ƒํ•˜๊ธฐ ์‰ฌ์šด ๊ตฌ์กฐ

๊ตฌ์ฒด์ ์ธ ๋ฌธ์ œ์ 

  • useState: ์ปดํฌ๋„ŒํŠธ๋ณ„ ๋กœ์ปฌ ์ƒํƒœ๋กœ ์ธํ•œ ๋™๊ธฐํ™” ๋ฌธ์ œ
  • useContext: Provider ์ค‘์ฒฉ์œผ๋กœ ์ธํ•œ ๋ณต์žก์„ฑ ์ฆ๊ฐ€
  • Props Drilling: ๊นŠ์€ ์ปดํฌ๋„ŒํŠธ ๊ณ„์ธต์—์„œ์˜ ์ƒํƒœ ์ „๋‹ฌ ๋ณต์žก์„ฑ
  • ์ƒํƒœ ์ผ๊ด€์„ฑ: ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ์—์„œ ๋™์ผํ•œ ์ƒํƒœ๋ฅผ ๋‹ค๋ฅด๊ฒŒ ๊ด€๋ฆฌ

โœ… ํ•ด๊ฒฐ ๋ฐฉ์•ˆ

Zustand๋ฅผ ํ™œ์šฉํ•œ ์ „์—ญ ์ƒํƒœ ๊ด€๋ฆฌ ๋„์ž…

  1. ์ „์—ญ Store ์„ค๊ณ„

    • ๊ฒ€์ƒ‰์–ด, ํ•„ํ„ฐ, ์ •๋ ฌ ์ƒํƒœ๋ฅผ ํ•˜๋‚˜์˜ store์—์„œ ํ†ตํ•ฉ ๊ด€๋ฆฌ
    • ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ๊ฐ€ store์—์„œ ์ง์ ‘ ์ƒํƒœ๋ฅผ ์ฝ๊ณ  ๋ณ€๊ฒฝ
    • props ์ „๋‹ฌ ์—†์ด๋„ ์ƒํƒœ ๊ณต์œ  ๊ฐ€๋Šฅ
  2. ์ปดํฌ๋„ŒํŠธ ๊ฐ„ ๊ฒฐํ•ฉ๋„ ๊ฐ์†Œ

    • ๊ฐ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋…๋ฆฝ์ ์œผ๋กœ store์— ์ ‘๊ทผ
    • ์ปดํฌ๋„ŒํŠธ ๊ฐ„ ์ง์ ‘์ ์ธ ์˜์กด์„ฑ ์ œ๊ฑฐ
    • ์ƒํƒœ ๋ณ€๊ฒฝ ์‹œ ์ž๋™์œผ๋กœ ๊ด€๋ จ ์ปดํฌ๋„ŒํŠธ๋งŒ ๋ฆฌ๋ Œ๋”๋ง
  3. ๊ตฌํ˜„ ์ฝ”๋“œ ์˜ˆ์‹œ

    // searchMoverStore.ts
    interface SearchMoverState {
      searchKeyword: string;
      selectedRegions: string[];
      selectedServiceTypes: string[];
      sortBy: "review" | "rating" | "career" | "completedCount";
    }
    
    export const useSearchMoverStore = create<SearchMoverState>((set) => ({
      searchKeyword: "",
      selectedRegions: [],
      selectedServiceTypes: [],
      sortBy: "review",
    
      setSearchKeyword: (keyword: string) => set({ searchKeyword: keyword }),
      setSelectedRegions: (regions: string[]) => set({ selectedRegions: regions }),
      setSelectedServiceTypes: (types: string[]) => set({ selectedServiceTypes: types }),
      setSortBy: (sortBy: string) => set({ sortBy: sortBy as any }),
    }));
  4. ์ปดํฌ๋„ŒํŠธ์—์„œ์˜ ์‚ฌ์šฉ

    // FilterBar.tsx
    const { selectedRegions, setSelectedRegions } = useSearchMoverStore();
    
    // SearchBar.tsx
    const { searchKeyword, setSearchKeyword } = useSearchMoverStore();
    
    // SortDropdown.tsx
    const { sortBy, setSortBy } = useSearchMoverStore();

๐ŸŽฏ ๊ฐœ์„  ํšจ๊ณผ

  • ์ฝ”๋“œ ๊ฐ„๊ฒฐ์„ฑ: props ์ „๋‹ฌ ์—†์ด ์ƒํƒœ ๊ณต์œ ๋กœ ์ฝ”๋“œ 75% ์ ˆ๊ฐ
  • ์œ ์ง€๋ณด์ˆ˜์„ฑ: ์ปดํฌ๋„ŒํŠธ ๊ฐ„ ๊ฒฐํ•ฉ๋„ ๊ฐ์†Œ๋กœ ๋ฒ„๊ทธ ๋ฐœ์ƒ ์œ„ํ—˜ ์ตœ์†Œํ™”
  • ํ™•์žฅ์„ฑ: ์ƒˆ๋กœ์šด ํ•„ํ„ฐ๋‚˜ ์ •๋ ฌ ์˜ต์…˜ ์ถ”๊ฐ€ ์‹œ ๊ธฐ์กด ์ฝ”๋“œ ์˜ํ–ฅ ์ตœ์†Œํ™”
  • ์„ฑ๋Šฅ: ํ•„์š”ํ•œ ์ปดํฌ๋„ŒํŠธ๋งŒ ๋ฆฌ๋ Œ๋”๋ง์œผ๋กœ ์„ฑ๋Šฅ ์ตœ์ ํ™”

๐Ÿ“š ๋ฐฐ์šด ์ 

  • ์ƒํƒœ ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ ํƒ์˜ ์ค‘์š”์„ฑ: ๋ณต์žกํ•œ ์ƒํƒœ๋Š” ์ ์ ˆํ•œ ๋„๊ตฌ๋กœ ๊ด€๋ฆฌ
  • Props Drilling ํšŒํ”ผ: ๊นŠ์€ ์ปดํฌ๋„ŒํŠธ ๊ณ„์ธต์—์„œ์˜ ์ƒํƒœ ์ „๋‹ฌ ๋ฌธ์ œ ํ•ด๊ฒฐ
  • ์ปดํฌ๋„ŒํŠธ ์„ค๊ณ„: ์ƒํƒœ ์˜์กด์„ฑ์„ ๊ณ ๋ คํ•œ ์ปดํฌ๋„ŒํŠธ ๊ตฌ์กฐ ์„ค๊ณ„
  • ์œ ์ง€๋ณด์ˆ˜์„ฑ ์šฐ์„ : ์ฝ”๋“œ์˜ ๋ณต์žก์„ฑ๋ณด๋‹ค ์œ ์ง€๋ณด์ˆ˜์„ฑ๊ณผ ํ™•์žฅ์„ฑ ๊ณ ๋ ค
  • ์ƒํƒœ ์ผ๊ด€์„ฑ: ์ „์—ญ ์ƒํƒœ๋กœ ์ปดํฌ๋„ŒํŠธ ๊ฐ„ ์ƒํƒœ ๋™๊ธฐํ™” ๋ณด์žฅ

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 7

Languages