ํฌ๋ช ํ๊ณ ํฉ๋ฆฌ์ ์ธ ์ด์ฌ! ๋ฌด๋น ๋ณด๋ฌ๊ฐ๊ธฐ -> ๋ฌด๋น
Back-end ๊นํ๋ธ Back-end
- ํ๋ก์ ํธ ์๊ฐ
- ๊ธฐ๋ฅ ๊ตฌํ ์์
- ์์คํ ์ํคํ ์ฒ
- ๊ธฐ์ ์คํ
- ์ฃผ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
- ํ ์๊ฐ ๋ฐ ๋ฌธ์
- ๊ฐ์ธ๋ณ ์ฃผ์ ์์ ๋ด์ญ
- ํ๋ก์ ํธ ๊ตฌ์กฐ
- ์ฃผ์ ๊ธฐ๋ฅ ์์ธ
- ์ฑ๋ฅ ์ต์ ํ ์ ๋ต
- ํธ๋ฌ๋ธ ์ํ
- ๊ธฐ์กด ์ด์ฌ ๊ณผ์ ์ ๋ฒ๊ฑฐ๋ก์ด ๊ฒฌ์ ์์ฒญ๊ณผ ๊ฐ๊ฒฉ ๋น๊ต์ ์ด๋ ค์์ ํด๊ฒฐํฉ๋๋ค. ๊ณ ๊ฐ์ด ์ด์ฌ ์ ๋ณด๋ฅผ ๋ฑ๋กํ๋ฉด, ๊ฒ์ฆ๋ ์ด์ฌ์ ์ฒด๋ค์ด ๊ฒฝ์์ ์ผ๋ก ๊ฒฌ์ ์ ์ ์ํฉ๋๋ค. ๊ณ ๊ฐ์ ๋ค์ํ ๊ฒฌ์ ๊ณผ ์กฐ๊ฑด์ ํ๋์ ๋น๊ตํด ํฉ๋ฆฌ์ ์ธ ์ ํ์ด ๊ฐ๋ฅํ๋ฉฐ, ๋ฆฌ๋ทฐ๋ฅผ ํตํด ์ ์ฒด ์ ๋ขฐ๋๋ ํ์ธํ ์ ์์ต๋๋ค. ์ด๋ฅผ ํตํด ํฌ๋ช ํ๊ณ ๊ณต์ ํ ์ด์ฌ ์ค๋น์ ๋น์ฉ ์ ๊ฐ์ ์ง์ํฉ๋๋ค.
๋ฐ์ดํฐ ํจ์นญ / ์บ์ฑ
- @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 | ๋ณด๊ณ ์ |
๐ ํ ๋ ธ์
๐ API ๋ฌธ์
๐ ๊น์ฌ์ฑ
-
๋ฆฌ๋ทฐ ํ์ด์ง
- ์์ฑ ๊ฐ๋ฅํ ๋ฆฌ๋ทฐ ํ์ด์ง + ๋ฆฌ๋ทฐ ์์ฑ ๋ชจ๋ฌ
- ๋ด๊ฐ ์์ฑํ ๋ฆฌ๋ทฐ ๋ชฉ๋ก ํ์ด์ง
React Query๊ธฐ๋ฐ ์ํ ๊ด๋ฆฌ- ๋ณ์ ์ ํ + ํ ์คํธ ์ ๋ ฅ ๊ฒ์ฆ
-
์ค์๊ฐ ์๋ฆผ ์์คํ
event-source-polyfill๊ธฐ๋ฐ SSE ์ค์๊ฐ ์๋ฆผZustand์๋ฆผ ๋ฐ์ดํฐ ์ํ ๊ด๋ฆฌReact Query์๋ฆผ ๋ชฉ๋ก ์ํ ๊ด๋ฆฌ- ์ค์๊ฐ ์๋ฆผ ์์ + ํ์
-
๊ณตํต ์ปดํฌ๋ํธ
- Input ๊ณตํต ์ปดํฌ๋ํธ ์ ์
- ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ UI ์ปดํฌ๋ํธ ์ค๊ณ
-
๋ฆฌ๋ทฐ ์์คํ
- Prisma ๋ฏธ๋ค์จ์ด๋ก ๊ฒฌ์ ํ์ ์ ๋น ๋ฆฌ๋ทฐ ์๋ ์์ฑ
- ์ด์ฌ ์๋ฃ ํ ๋ฆฌ๋ทฐ ์์ฑ ๊ฐ๋ฅ (๋น ๋ฆฌ๋ทฐ PATCH)
- 1:1 ๊ด๊ณ ๋ณด์ฅ (ํ๋์ ํ์ ๊ฒฌ์ ๋น ํ๋์ ๋ฆฌ๋ทฐ)
-
์ค์๊ฐ ์๋ฆผ ์์คํ
- ์ก์ ํ ์ด๋ธ ๊ธฐ๋ฐ ๋ก๊ทธ/์๋ฆผ ๊ด๋ฆฌ (ํ์ฅ์ฑ ๊ณ ๋ ค)
- Prisma ๋ฏธ๋ค์จ์ด๋ก ์ก์ ์์ฑ ๊ฐ์ง + ์๋ฆผ ์๋ ์์ฑ
- SSE ์ค์๊ฐ ์๋ฆผ ์ ์ก
๐ ๊น์น์ค
-
๊ฒฌ์ ์์ฒญ ํ์ด์ง
- ํ์ฑ ๊ฒฌ์ ์ํ์ ๋ฐ๋ฅธ ์ค๋งํธ ๋ผ์ฐํ (์์ฑ/์์ ์๋ ๋ถ๊ธฐ)
- ์ฑํ ํ 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 ๊ธฐ๋ฐ ํ์์กด ์ด์ ํด๊ฒฐ
-
๊ฒฌ์ ์์ฒญ ์์คํ
- ๊ฒฌ์ ์์ฒญ CRUD API (์์ฑ/์กฐํ/์์ /์ทจ์)
- ๊ถํ/ํ๋กํ ํ์ธ + ์ ๋ ฅ ๊ฒ์ฆ (์ด์ฌ์ผ, ์ฃผ์, ์ด์ฌ์ ํ)
- Sentry ์๋ฌ ๋ชจ๋ํฐ๋ง + ์บ์ ๋ฌดํจํ ์ฒ๋ฆฌ
-
์ค์ผ์ค๋ฌ ์์คํ
- ๊ธฐ์ฌ๋ ์๋ณ ์ค์ผ์ค ์กฐํ API + Redis ์บ์ฑ (60์ด TTL)
- ์๋ํ ์ค์ผ์ค๋ฌ: ๋ง๋ฃ ์ฒ๋ฆฌ (00์), ์๋ฆผ ์์ฑ (09์)
-
์ฐํ๊ธฐ ์์คํ
- ์ฐํ๊ธฐ CRUD API + ์ํํธ ์ญ์ /๋ฉฑ๋ฑ์ฑ ๋ณด์ฅ
- DB ๋ชจ๋ธ: ๊ณ ๊ฐ-๊ธฐ์ฌ ์ค๋ณต ๋ฐฉ์ง (@@unique ์ ์ฝ)
- CUSTOMER ๊ถํ ์ ์ฉ + ๊ด๋ จ ์บ์ ๋ฌดํจํ
-
๋ค๊ตญ์ด ์์คํ
- ๋ฒ์ญ ๋ฏธ๋ค์จ์ด (?lang=ko|en|zh) + excludeKeys ์ฒ๋ฆฌ
- ๋ฒ์ญ ์คํจ ์ ์๋ณธ ์๋ต + Sentry ๊ธฐ๋ก
-
Redis ์บ์ฑ
- GET ์์ฒญ ์บ์ฑ + ์ฌ์ฉ์๋ณ ๋ถ๊ธฐ (varyByAuth)
- ์บ์ ํค ๊ท์น + ์๋ ๋ฌดํจํ ์์คํ
๐ ๋ฐฑ์ง์ฐ
-
๊ธฐ์ฌ๋ ์ฐพ๊ธฐ ํ์ด์ง
- ์ค์๊ฐ ๋๋ฐ์ด์ฑ ๊ฒ์ (100ms ์ง์ฐ) + ๋๋กญ๋ค์ด ํํฐ๋ง
- ๋ฌดํ์คํฌ๋กค (
react-intersection-observer+useInfiniteQuery) + 4๊ฐ์ฉ ํ์ด์ง๋ค์ด์ Zustand์ํ ๊ด๋ฆฌ (๊ฒ์/ํํฐ/์ ๋ ฌ)- ์ฐํ๊ธฐ ๊ธฐ๋ฅ + PC ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ฌ์ด๋๋ฐ
useMemo์ฑ๋ฅ ์ต์ ํ + Sentry ์๋ฌ ์ถ์
-
๊ธฐ์ฌ๋ ์์ธ ํ์ด์ง
- ๋์ ๋ผ์ฐํ
(
useParams) + ์์ธ ์ ๋ณด ์กฐํ - ํ๋กํ/๊ฒฝ๋ ฅ/ํ์ /๋ฆฌ๋ทฐ ์ ๋ณด ํ์
- ์๋น์ค ํ์ /์ง์ญ ๋ค๊ตญ์ด ์ฒ๋ฆฌ
- ๊ฒฌ์ ์์ฒญ/์ง์ ๊ฒฌ์ /์ฐํ๊ธฐ ์ก์ ๋ฒํผ
- ์์ ๊ณต์ (์นด์นด์คํก/ํ์ด์ค๋ถ/๋งํฌ)
- ๋์ ๋ผ์ฐํ
(
-
๋๋ฉ ํ์ด์ง
- 4๋จ๊ณ ์๋น์ค ์ด์ฉ ๊ณผ์ ์ธํฌ๊ทธ๋ํฝ
- ๋ฐ์ํ ์ด๋ฏธ์ง ์ฒ๋ฆฌ (
Next.js Image+ WebP) - CSS ์ ๋๋ฉ์ด์ (ํธ๋ญ ์ฌ๋ผ์ด๋์ธ, ํ์ค ํจ๊ณผ)
- ์ ๊ทผ์ฑ (ARIA, ์๋งจํฑ HTML, ์คํต ๋งํฌ)
-
๊ธฐ์ฌ๋ ์กฐํ ์์คํ
- REST API: ๋ชฉ๋ก/์์ธ/์ฐํ๋ชฉ๋ก ์กฐํ
optionalAuth๋ฏธ๋ค์จ์ด๋ก ๋ก๊ทธ์ธ/๋น๋ก๊ทธ์ธ ์ง์- Prisma ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์ /ํํฐ๋ง/์ ๋ ฌ
-
๊ฒ์/ํํฐ ์์คํ
- ๋๋ค์/์ง์ญ ๊ธฐ๋ฐ ํ ์คํธ ๊ฒ์ (OR ์กฐ๊ฑด)
- ์ง์ญ๋ณ/์๋น์คํ์ ๋ณ ํํฐ๋ง + DB ์ธ๋ฑ์ค ์ต์ ํ
- ๋ฆฌ๋ทฐ์/ํ์ /๊ฒฝ๋ ฅ/์๋ฃ๊ฑด์ ๊ธฐ์ค ์ ๋ ฌ
-
๋ค๊ตญ์ด/๋ชจ๋ํฐ๋ง
- ๋ฒ์ญ ๋ฏธ๋ค์จ์ด (?lang=ko|en|zh)
- Sentry ์๋ฌ ์ถ์ + ์ก์ ๋ก๊ทธ
- ๊ถํ ๊ฒ์ฆ + ๋น์ฆ๋์ค ๊ท์น ๊ฒ์ฆ
๐ ๊น์๋น
-
์ ์ - ๊ฒฌ์ ๊ด๋ฆฌ ํ์ด์ง
- ์ด์ฌ ๊ฒฌ์ ํ์ , ๋ฐ๋ ค ๊ธฐ๋ฅ ๊ตฌํ
- ๊ฐ๋จํ ๊ฒฌ์ , ์ด์ฌ, ๊ธฐ์ฌ ์ ๋ณด๊ฐ ํฌํจ๋ ์นด๋ ๋ฆฌ์คํธ UI ๊ตฌํ
React Query๋ฅผ ์ด์ฉํ ์ง์ ์ ์ธ API ํธ์ถ- ์ ํธํจ์๋ฅผ ํตํ ์์ ์ ์ธ ๋ค๊ตญ์ด ๋ ์ง ์ฒ๋ฆฌ (formatDateWithDay)
-
๊ธฐ์ฌ - ๊ฒฌ์ ๊ด๋ฆฌ ํ์ด์ง
- ๊ฒฌ์ ๋ฐ๊ธฐ, ๋ฐ๋ ค ๊ธฐ๋ฅ ๊ตฌํ
- ๊ฐ๋จํ ๊ฒฌ์ , ์ด์ฌ, ๊ธฐ์ฌ ์ ๋ณด๊ฐ ํฌํจ๋ ์นด๋ ๋ฆฌ์คํธ UI ๊ตฌํ
React Query๋ฅผ ์ด์ฉํ ์ง์ ์ ์ธ API ํธ์ถ- ์ ํธํจ์๋ฅผ ํตํ ์์ ์ ์ธ ๋ค๊ตญ์ด ๋ ์ง ์ฒ๋ฆฌ (formatDateWithDay)
- React ํ ์ ์ด์ฉํ ๋น์ ์ด ์ปดํฌ๋ํธ ๋ฆฌ๋ ๋๋ง ๋ฐฉ์ง
-
๊ณตํต Chip ์ปดํฌ๋ํธ
- ๋ฐ์คํ ๋ฐ ํด๋ฆญ ์ฌ๋ถ ๋ฑ์ ํฌํจํ ์กฐ๊ฑด๋ถ ๋ ๋๋ง์ ๊ณ ๋ คํ ์ค๊ณ
-
SNS ๊ณต์ ๊ธฐ๋ฅ
- ์นด์นด์ค SDK๋ฅผ ์ด์ฉํ ์นด์นด์ค API ์ฌ์ฉ
- ํผ๋ ํํ๋ก ์ ๋ชฉ, ์ค๋ช , ์ด๋ฏธ์ง, ๋งํฌ ํฌํจ
- ์คํจ ์ ํด๋ฆฝ๋ณด๋ ๋ณต์ฌ๋ก ๋์ฒด
- ํ์ด์ค๋ถ ๊ณต์ API URL ์ฌ์ฉ
- Open Graph ๋ฉํ ํ๊ทธ๋ฅผ ๋์ ์ผ๋ก ์ค์ ํ์ฌ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ๊ฐ์
- ๊ณต์ ํ ์คํธ๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌํ์ฌ ์ฌ์ฉ์ ํธ์์ฑ ์ ๊ณต
- ํ์ ์ฐฝ์ผ๋ก ๊ณต์ ํ์ด์ง ์ด๊ธฐ
-
์ ์ ๊ฒฌ์ ๊ด๋ฆฌ API
- ๊ฒฌ์ ์กฐํ ๋ฐ ํ์ , ๊ฑฐ์ API ๊ตฌํ
- MVC ํจํด์ผ๋ก ๋ช ํํ ๊ณ์ธต ๋ถ๋ฆฌ
- Redis ์บ์ฑ์ ํตํ ๋น ๋ฅธ ์๋ต ์๋ ๋ฐ DB ๋ถํ ๊ฐ์
- ํธ๋์ญ์ ์ฒ๋ฆฌ๋ก ์ธํ ๋ฐ์ดํฐ ์ผ๊ด์ฑ ๋ณด์ฅ
-
๊ธฐ์ฌ ๊ฒฌ์ ๊ด๋ฆฌ API
- ๊ฒฌ์ ์กฐํ ๋ฐ ๊ฒฌ์ ๋ณด๋ด๊ธฐ, ๋ฐ๋ คํ๊ธฐ API ๊ตฌํ
- MVC ํจํด์ผ๋ก ๋ช ํํ ๊ณ์ธต ๋ถ๋ฆฌ
- Redis ์บ์ฑ์ ํตํ ๋น ๋ฅธ ์๋ต ์๋ ๋ฐ DB ๋ถํ ๊ฐ์
- ํธ๋์ญ์ ์ฒ๋ฆฌ๋ก ์ธํ ๋ฐ์ดํฐ ์ผ๊ด์ฑ ๋ณด์ฅ
-
์ค์ผ์ค๋ฌ ์์คํ
node-cron์ ํตํ ์ค์ผ์ค๋ฌ ์์ฑ- ์๋ฒ ์์ ์ ์ค์ผ์ค๋ฌ ์ด๊ธฐํ ๋ฐ Sentry๋ฅผ ํตํ ์๋ฌ ์ถ์
- ๋ชจ๋ ํฌ๋ก ์์ ์ ์ค์ ์ง์ค์์ผ๋ก ๊ด๋ฆฌ, ๊ฐ ์์ ๋ณ ๋ช ํํ ํจ์ ๋ถ๋ฆฌ
๐ ์ค์ธ์ค
-
๊ณตํต ํ์ด์ง๋ค์ด์ ์์คํ
usePagination์ปค์คํ ํ + ๋ณต์กํ ํ์ด์ง๋ค์ด์ ๋ก์ง- ๋ฐ์ํ ๋์์ธ (sm/lg ํฌ๊ธฐ ์ต์ )
useMemo์ฑ๋ฅ ์ต์ ํ + ์ฌ์ฌ์ฉ์ฑ ์ ๊ณต- ์ฌ๋ฌ ํ์ด์ง์์ ์ผ๊ด๋ ํ์ด์ง๋ค์ด์ ๊ฒฝํ
-
๊ธฐ์ฌ๋ ๋ง์ดํ์ด์ง
- ํตํฉ ๋ฐ์ดํฐ ๋ก๋ฉ (์ฌ์ฉ์ ์ ๋ณด + ๊ธฐ์ฌ๋ ์ ๋ณด ๋ณ๋ ฌ ์ฒ๋ฆฌ)
- ๋ฐ์ํ ๋ ์ด์์ (๋ฐ์คํฌํฑ/๋ชจ๋ฐ์ผ ์ต์ ํ)
- ๋จ๊ณ๋ณ ์๋ฌ ์ํ ๊ด๋ฆฌ + ์์ ์ ์ธ ์ฌ์ฉ์ ๊ฒฝํ
-
๊ธฐ์ฌ๋ ํ๋กํ ์์ ํ์ด์ง
- Presigned URL ๊ธฐ๋ฐ S3 ์ ๋ก๋ (๋ณด์์ฑ + ์ฑ๋ฅ)
- FileReader API ์ค์๊ฐ ์ด๋ฏธ์ง ๋ฏธ๋ฆฌ๋ณด๊ธฐ
React Hook Form๋ณต์กํ ํผ ์ํ ๊ด๋ฆฌ + ์ ํจ์ฑ ๊ฒ์ฌ
-
๊ธฐ์ฌ๋ ๊ธฐ๋ณธ์ ๋ณด ์์ ํ์ด์ง
- ์กฐ๊ฑด๋ถ ๋ ๋๋ง (์ผ๋ฐ/์์ ๋ก๊ทธ์ธ ์ฌ์ฉ์๋ณ UI)
- ๋์ ๋ ์ด์์ (์ฌ์ฉ์ ํ์ ๋ณ 1์ด/2์ด ์๋ ์ ํ)
- ๋ก๊ทธ์ธ ๋ฐฉ์๋ณ ์กฐ๊ฑด๋ถ ๊ฒ์ฆ ๋ก์ง
-
ํ์ด์ง๋ค์ด์ API
- ํ์ค ํ์ด์ง๋ค์ด์ API ๊ตฌ์กฐ
- ํ์ด์ง ๊ธฐ๋ฐ ๋ฐ์ดํฐ ์กฐํ ์์คํ
-
๊ธฐ์ฌ๋ ๋ง์ดํ์ด์ง API
- ํตํฉ API ์ค๊ณ (ํ์ํ ๋ชจ๋ ๋ฐ์ดํฐ ํจ์จ์ ์กฐํ)
- Prisma ๋ณต์กํ ๊ด๊ณ ๋ฐ์ดํฐ ์กฐํ ์ต์ ํ
-
๊ธฐ์ฌ๋ ํ๋กํ ๊ด๋ฆฌ ์์คํ
- S3 ํด๋ผ์ด์ธํธ ์์ ํ ํ์ผ ์ ๋ก๋
- ํ๋กํ ๋ฐ์ดํฐ ์ ํจ์ฑ ๊ฒ์ฌ + ๋น์ฆ๋์ค ๋ก์ง
-
๊ธฐ์ฌ๋ ๊ธฐ๋ณธ์ ๋ณด ๊ด๋ฆฌ ์์คํ
- ์ฌ์ฉ์ ํ์ ๋ณ ์ฒ๋ฆฌ (์ผ๋ฐ/์์ ๋ก๊ทธ์ธ ๋ถ๋ฆฌ)
- bcrypt ๋น๋ฐ๋ฒํธ ํด์ฑ + ๊ฒ์ฆ
- ๋ฏผ๊ฐ์ ๋ณด ์ํธํ ์ ์ฅ (์ ํ๋ฒํธ ๋ฑ)
๐ ๋ฐ๋ฏผ๊ท
-
๋ก๊ทธ์ธ/ํ์๊ฐ์ ํ์ด์ง
- ์ด์ค ์ฌ์ฉ์ ํ์ ์ง์ (CUSTOMER/MOVER ๋ณ๋ ํ์ด์ง)
- ์์ ๋ก๊ทธ์ธ: Google, Kakao, Naver OAuth ์ฐ๋
React Hook Form+ ์ค์๊ฐ ์ ํจ์ฑ ๊ฒ์ฌ- 14๋ถ ์ฃผ๊ธฐ ์๋ ํ ํฐ ๊ฐฑ์ , ์ค๋ณต ๊ฐฑ์ ๋ฐฉ์ง
next-intl๊ธฐ๋ฐ ๋ค๊ตญ์ด ์ง์ + ARIA ์ ๊ทผ์ฑ
-
ํ๋กํ ๋ฑ๋ก ํ์ด์ง
- ์ฌ์ฉ์ ํ์ ๋ณ ๋ง์ถค ํผ (CUSTOMER/MOVER)
- S3 ์ง์ ์ ๋ก๋ + CloudFront URL ์์ฑ
- ํ์ผ๋ช ํ์์คํฌํ ๋ณ๊ฒฝ์ผ๋ก ๋ณด์ ๊ฐํ
- 17๊ฐ ์๋/3๊ฐ ์๋น์ค ํ์ ์ ํ UI
- ๊ธ์ ์ ์นด์ดํฐ + ์ค์๊ฐ ๊ฒ์ฆ
-
ํ๋กํ ์์ ํ์ด์ง
- ๊ธฐ์กด ์ ๋ณด ์๋ ๋ก๋ฉ + ํ์ ๋ณ ๋ถ๊ธฐ ์ฒ๋ฆฌ
- ์์ ๋ก๊ทธ์ธ ๊ณ์ ๋ณด์ ์ฒ๋ฆฌ (์ด๋ฉ์ผ ์ฝ๊ธฐ์ ์ฉ, ๋น๋ฐ๋ฒํธ ๋ณ๊ฒฝ ์ ํ)
- ํ์ฌ/์ ๋น๋ฐ๋ฒํธ ๊ต์ฐจ ๊ฒ์ฆ
- ์์ ์๋ฃ ํ ์ ์ญ ์ฌ์ฉ์ ์ ๋ณด ๊ฐฑ์
-
์ธ์ฆ ์์คํ
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 ์ ๊ทผ์ฑ
- DOMPurify HTML ์ ํ +
-
์๋ฆผ ์ ํ
- ์ผ๋ฐ ์ ์ : ์ ๊ฒฌ์ ๋์ฐฉ / ๊ฒฌ์ ํ์ / ์ด์ฌ ๋น์ผ ์๋ฆผ
- ๊ธฐ์ฌ๋: ์ ๊ฒฌ์ ์์ฒญ / ๊ฒฌ์ ํ์ / ์ด์ฌ ๋น์ผ ์๋ฆผ
- ํด๋ฆญ ์ ํด๋น ํ์ด์ง๋ก ์๋ ์ด๋ + ์ฝ์ ์ฒ๋ฆฌ
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 ์ธก๋ฉด์์์ ๋์ ๋ฐฉ์ ์ ํ
-
๋ฏธ๋ค์จ์ด ๋ก์ง ์์ ๋์ UX ๊ฐ์
- ๋ฏธ๋ค์จ์ด ์์ ์ ์์์น ๋ชปํ ์ฌ์ด๋ ์ดํํธ ์ฐ๋ ค
- ์ ์ฒด ์ธ์ฆ ํ๋ฆ์ ์ํฅ์ ์ค ์ ์๋ ์ํ์ฑ ๊ณ ๋ ค
-
์ ํ ๋ฒํผ ํ์ฑํ ์กฐ๊ฑด ์ ํ
- ์์ชฝ ๋ชจ๋ ํ๋กํ ๋ฑ๋ก ์๋ฃํ ๊ฒฝ์ฐ์๋ง ์ ํ ๋ฒํผ ํ์ฑํ
- ์๋ชป๋ ํ๋ฆ์ ์ฌ์ ์ ์ฐจ๋จํ์ฌ UX ์์ ์ฑ ํ๋ณด
- ํ ์ชฝ ์ญํ ๋ง ์ฌ์ฉํ ๊ฒฝ์ฐ ๋ฐ๋์ชฝ ๊ณ์ ์ฌ์ฉ ๋ถํ์ํ๋ค๋ ํ๋จ
-
ํ ์คํธ ๋ชจ๋ฌ์ ํตํ ์ฆ๊ฐ์ ํผ๋๋ฐฑ
- ์ ํ ์๋ฃ ์ ์ฆ์ ์๋ฃ ๋ฉ์์ง ์ ๊ณต
- ์ฌ์ฉ์๊ฐ ํ์ฌ ์ํ๋ฅผ ๋ช ํํ ์ธ์งํ ์ ์๋๋ก UX ๊ฐ์
- ํ์ด์ง ๊น๋นก์ ํ์ ์ต์ํ
- ์ฟ ํค ๊ธฐ๋ฐ ์ธ์ฆ์ ํ์ด๋ฐ ์ด์: ๋ฏธ๋ค์จ์ด์ ์ฟ ํค ๊ฐฑ์ ๊ฐ์ ๋๊ธฐํ ๋ฌธ์
- UX vs ๊ธฐ์ ์ ํด๊ฒฐ์ ํธ๋ ์ด๋์คํ: ์๋ฒฝํ ๊ธฐ์ ์ ํด๊ฒฐ๋ณด๋ค ์์ ์ ์ธ UX ์ฐ์
- ์ฌ์ ๋ฐฉ์ง์ ์ค์์ฑ: ๋ฌธ์ ๋ฐ์ ํ ์์ ๋ณด๋ค ๋ฐ์ ๊ฐ๋ฅ์ฑ์ ์ฐจ๋จํ๋ ์ค๊ณ
- ์ฌ์ฉ์ ์ค์ฌ ์ฌ๊ณ : ๊ธฐ์ ์ ์๋ฒฝํจ๋ณด๋ค ์ค์ ์ฌ์ฉ์ ๊ฒฝํ ๊ฐ์ ์ ์ง์ค
2. [์๋ฆผ ์์คํ ] HTML ๊ธฐ๋ฐ ์๋ฆผ ๋ฉ์์ง์ XSS ๊ณต๊ฒฉ ์ํ๊ณผ ํด๊ฒฐ ๋ฐฉ์
- ๋ฐฑ์๋์์ HTML ๋ฌธ์์ด๋ก ๊ฐ๊ณต๋ ์๋ฆผ ๋ฉ์์ง๋ฅผ ํ๋ก ํธ์๋๋ก ์ ๋ฌ
- ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ถํ ๊ฐ์์ ๋ฉ์์ง ์คํ์ผ๋ง ์ฉ์ด์ฑ์ ์ํ ์ค๊ณ
- ํ์ง๋ง ์ ์ฑ ์คํฌ๋ฆฝํธ๊ฐ ์ฝ์ ๋ ๊ฒฝ์ฐ ๊ทธ๋๋ก ์คํ๋๋ ์ฌ๊ฐํ ๋ณด์ ์ํ
<script>ํ๊ทธ๋ฅผ ํตํ ์ ์ฑ ์ฝ๋ ์คํonerror,onclick๋ฑ ์ด๋ฒคํธ ํธ๋ค๋ฌ ์ฝ์<iframe>๋ฑ ์ธ๋ถ ๋ฆฌ์์ค ๋ก๋ฉ- CSS ํํ์์ ํตํ ๋ฐ์ดํฐ ํ์ทจ
HTML ๋ฌธ์์ด ์ง์ ๋ ๋๋ง์ ๋ณด์ ์ทจ์ฝ์
โ ๋ฐฑ์๋์์ HTML ํ๊ทธ์ ์คํ์ผ์ด ํฌํจ๋ ๋ฉ์์ง ์์ฑ
โ ํ๋ก ํธ์๋์์ HTML์ ๊ฒ์ฆ ์์ด ์ง์ ๋ ๋๋ง
โ ์ ์ฑ ์คํฌ๋ฆฝํธ๊ฐ ํฌํจ๋ ๊ฒฝ์ฐ ์ฆ์ ์คํ๋์ด XSS ๊ณต๊ฒฉ ๋ฐ์
๊ตฌ์ฒด์ ์ธ ๋ฌธ์ ์
- HTML ์ฝํ ์ธ ์ ์ ๋ขฐ์ฑ ๊ฒ์ฆ ๋ถ์ฌ
- ์คํฌ๋ฆฝํธ ์คํ ๋ฐฉ์ง ๋ฉ์ปค๋์ฆ ์์
- ์ฌ์ฉ์ ์ ๋ ฅ ๋ฐ์ดํฐ์ ์์ ์ฑ ๋ณด์ฅ ๋ถ๊ฐ
์ด์ค ๋ฐฉ์ด ์ฒด๊ณ ๊ตฌ์ถ
-
DOMPurify๋ฅผ ํตํ HTML ์๋ํ์ด์ง
<script>,<iframe>๋ฑ ์ํ ์์ ์๋ ์ ๊ฑฐonerror,onclick๋ฑ ์ด๋ฒคํธ ํธ๋ค๋ฌ ์ฐจ๋จ- ํ์ฉ๋ HTML ํ๊ทธ๋ง ํ์ดํธ๋ฆฌ์คํธ ๋ฐฉ์์ผ๋ก ํต๊ณผ
- XSS ๊ณต๊ฒฉ ๊ฒฝ๋ก ์์ฒ ์ฐจ๋จ
-
html-react-parser๋ฅผ ํตํ ์์ ํ ๋ ๋๋ง
- ์๋ํ์ด์ง๋ HTML์ React ์ปดํฌ๋ํธ๋ก ๋ณํ
- React์ ๋ด์ฅ ๋ณด์ ๋ฉ์ปค๋์ฆ ํ์ฉ
- ์์ ํ DOM ์กฐ์ ๋ฐ ์ด๋ฒคํธ ์ฒ๋ฆฌ
-
๊ตฌํ ์ฝ๋ ์์
// 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 ์๋ฒ ๋ ๋ฐฉ์์ผ๋ก ์ ํ
-
์นด์นด์ค ์ฃผ์ API embed ํจ์ ํ์ฉ
- ํ์ ๋์ ํ์ด์ง ๋ด์ ์ง์ ์ฃผ์ ๊ฒ์์ฐฝ ์ฝ์
daum.Postcode๊ฐ์ฒด์embed๋ฉ์๋ ์ฌ์ฉ- ์ง์ ๋ ์ปจํ ์ด๋ ์์ญ์ ์ฃผ์ ๊ฒ์์ฐฝ ๋ ๋๋ง
-
๋์ ์คํฌ๋ฆฝํธ ๋ก๋ฉ ๋ฐ ์์ ์ฑ ํ๋ณด
- ํ์ํ ๋๋ง ์นด์นด์ค API ์คํฌ๋ฆฝํธ ๋์ ๋ก๋
- ์คํฌ๋ฆฝํธ ๋ก๋ ์๋ฃ ํ
new daum.Postcode๊ฐ์ฒด ์์ฑ embed๋ฉ์๋๋ก ์ปจํ ์ด๋์ ์ฃผ์ ๊ฒ์์ฐฝ ์ฝ์
-
๊ตฌํ ์ฝ๋ ์์
// ์นด์นด์ค 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;
-
๋ฆฌ๋ ๋๋ง ์์ ์ฑ ํ๋ณด
- ํ์ด์ง ๋ฆฌ๋ ๋๋ง ์ iframe ์ํ ํ์ธ
- iframe์ด ์ฌ๋ผ์ง ๊ฒฝ์ฐ ์ฌ์๋ ๋ก์ง ๊ตฌํ
- ์์ ์ ์ธ ์ฃผ์ ๊ฒ์ UX ์ ๊ณต
- ๋ธ๋ผ์ฐ์ ํธํ์ฑ: ํ์ ์ฐจ๋จ ์ ์ฑ ์ ์ํฅ๋ฐ์ง ์๋ ์์ ์ ์ธ ๋์
- ์ฌ์ฉ์ ๊ฒฝํ: ํ์ ํ์ฉ ์ค์ ์์ด๋ ์ฆ์ ์ฃผ์ ๊ฒ์ ๊ฐ๋ฅ
- ๊ธฐ๋ฅ ์์ ์ฑ: iframe ๊ธฐ๋ฐ์ผ๋ก ์ผ๊ด๋ ์ฃผ์ ๊ฒ์ ๊ธฐ๋ฅ ์ ๊ณต
- ๋ชจ๋ฐ์ผ ๋์: ๋ชจ๋ฐ์ผ ๋ธ๋ผ์ฐ์ ์์๋ ๋์ผํ UX ์ ๊ณต
- ๋ธ๋ผ์ฐ์ ์ ์ฑ ๋์: ํ์ ์ฐจ๋จ ๋ฑ ๋ธ๋ผ์ฐ์ ์ ์ฝ์ฌํญ ์ฌ์ ๊ณ ๋ ค
- ๋์ ๊ธฐ์ ํ์ฉ:
window.open๋์iframe์๋ฒ ๋ ๋ฐฉ์ ๋์ - ๋์ ๋ก๋ฉ ์ต์ ํ: ํ์ ์์ ์๋ง ์ธ๋ถ ์คํฌ๋ฆฝํธ ๋ก๋
- ์์ ์ฑ ํ๋ณด: ๋ฆฌ๋ ๋๋ง ์ํฉ์์๋ ์ผ๊ด๋ ๊ธฐ๋ฅ ๋์ ๋ณด์ฅ
4. [์ฑ๋ฅ ์ต์ ํ] GET API ์๋ต ์๋ ์ ํ ๋ฌธ์ ์ Redis ์บ์ฑ ์ ๋ต ๋์
- ๋ฐ์ ํ์: ์ผ๋ถ GET API, ํนํ ์์ฃผ ํธ์ถ๋๋ API์์ ์๋ต ์๋ ์ ํ
- ๋ถ์์ฉ: ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ถํ ์ฆ๊ฐ ๋ฐ ์ ์ฒด ์์คํ ์ฑ๋ฅ ์ ํ
- ์ํฅ ๋ฒ์: ์ฌ์ฉ์ ๊ฒฝํ ์ ํ ๋ฐ ์๋ฒ ๋ฆฌ์์ค ๋ญ๋น
- API ์๋ต ์๊ฐ ์ฆ๊ฐ (๋ช ์ด โ ์์ญ ์ด)
- ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ ํ ๊ณ ๊ฐ
- ๋์ ์ฌ์ฉ์ ์ฆ๊ฐ ์ ์ฑ๋ฅ ๊ธ๊ฒฉํ ์ ํ
๋ฐ๋ณต์ ์ธ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ ๊ทผ์ ๋นํจ์จ์ฑ
โ ๋์ผํ ์์ฒญ์ด ๋ฐ๋ณต์ ์ผ๋ก ๋ค์ด์ฌ ๋๋ง๋ค
โ ๋งค๋ฒ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ๊ทผํ์ฌ ๋ฐ์ดํฐ ์กฐํ
โ ๋ถํ์ํ ๋ฆฌ์์ค ๋ญ๋น๋ก ์ด์ด์ง
๊ตฌ์ฒด์ ์ธ ๋ฌธ์ ์
- ๋์ผํ ๋ฐ์ดํฐ์ ๋ํ ์ค๋ณต ์ฟผ๋ฆฌ ์คํ
- ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ ๋ฐ ์ฟผ๋ฆฌ ์ฒ๋ฆฌ ์ค๋ฒํค๋
- ๋ฉ๋ชจ๋ฆฌ ๋ฐ CPU ๋ฆฌ์์ค ๋ญ๋น
Redis ๊ธฐ๋ฐ ์บ์ฑ ์ ๋ต ๋์
-
Express ๋ฏธ๋ค์จ์ด ํํ์ ์บ์ ๋ก์ง ๊ตฌํ
- Redis๋ฅผ ์บ์ ์ ์ฅ์๋ก ํ์ฉ
cache๋ฏธ๋ค์จ์ด๋ฅผ ํน์ ๋ผ์ฐํฐ์ ์ถ๊ฐttlSeconds์ต์ ์ผ๋ก ์บ์ ์ ํจ ๊ธฐ๊ฐ ์ค์
-
์ฌ์ฉ์๋ณ ์บ์ ํค ๋ถ๋ฆฌ
varyByAuth: true์ต์ ์ ์ฉ- ์ธ์ฆ๋ ์ฌ์ฉ์ ์ฌ๋ถ์ ๋ฐ๋ผ ์บ์ ํค ๋ถ๋ฆฌ
- ์ฌ์ฉ์๋ณ๋ก ๋ค๋ฅธ ๋ด์ฉ์ด ์บ์ฑ๋๋๋ก ๋ณด์ฅ
-
๊ตฌํ ์ฝ๋ ์์
// estimateRequest.routes router.get( "/active", verifyAccessToken, cache({ ttlSeconds: 60, varyByAuth: true }), defaultTranslationMiddleware, (req, res) => estimateRequestController.getActiveEstimateRequest(req, res), );
-
์บ์ ๋ฌดํจํ ๋ฐ ๋ชจ๋ํฐ๋ง
- ๋ฐ์ดํฐ ๋ณ๊ฒฝ ์ ํจํด ๊ธฐ๋ฐ ์บ์ ์ญ์
- 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๋ฅผ ํ์ฉํ ์ ์ญ ์ํ ๊ด๋ฆฌ ๋์
-
์ ์ญ Store ์ค๊ณ
- ๊ฒ์์ด, ํํฐ, ์ ๋ ฌ ์ํ๋ฅผ ํ๋์ store์์ ํตํฉ ๊ด๋ฆฌ
- ๋ชจ๋ ์ปดํฌ๋ํธ๊ฐ store์์ ์ง์ ์ํ๋ฅผ ์ฝ๊ณ ๋ณ๊ฒฝ
- props ์ ๋ฌ ์์ด๋ ์ํ ๊ณต์ ๊ฐ๋ฅ
-
์ปดํฌ๋ํธ ๊ฐ ๊ฒฐํฉ๋ ๊ฐ์
- ๊ฐ ์ปดํฌ๋ํธ๊ฐ ๋ ๋ฆฝ์ ์ผ๋ก store์ ์ ๊ทผ
- ์ปดํฌ๋ํธ ๊ฐ ์ง์ ์ ์ธ ์์กด์ฑ ์ ๊ฑฐ
- ์ํ ๋ณ๊ฒฝ ์ ์๋์ผ๋ก ๊ด๋ จ ์ปดํฌ๋ํธ๋ง ๋ฆฌ๋ ๋๋ง
-
๊ตฌํ ์ฝ๋ ์์
// 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 }), }));
-
์ปดํฌ๋ํธ์์์ ์ฌ์ฉ
// FilterBar.tsx const { selectedRegions, setSelectedRegions } = useSearchMoverStore(); // SearchBar.tsx const { searchKeyword, setSearchKeyword } = useSearchMoverStore(); // SortDropdown.tsx const { sortBy, setSortBy } = useSearchMoverStore();
- ์ฝ๋ ๊ฐ๊ฒฐ์ฑ: props ์ ๋ฌ ์์ด ์ํ ๊ณต์ ๋ก ์ฝ๋ 75% ์ ๊ฐ
- ์ ์ง๋ณด์์ฑ: ์ปดํฌ๋ํธ ๊ฐ ๊ฒฐํฉ๋ ๊ฐ์๋ก ๋ฒ๊ทธ ๋ฐ์ ์ํ ์ต์ํ
- ํ์ฅ์ฑ: ์๋ก์ด ํํฐ๋ ์ ๋ ฌ ์ต์ ์ถ๊ฐ ์ ๊ธฐ์กด ์ฝ๋ ์ํฅ ์ต์ํ
- ์ฑ๋ฅ: ํ์ํ ์ปดํฌ๋ํธ๋ง ๋ฆฌ๋ ๋๋ง์ผ๋ก ์ฑ๋ฅ ์ต์ ํ
- ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ ํ์ ์ค์์ฑ: ๋ณต์กํ ์ํ๋ ์ ์ ํ ๋๊ตฌ๋ก ๊ด๋ฆฌ
- Props Drilling ํํผ: ๊น์ ์ปดํฌ๋ํธ ๊ณ์ธต์์์ ์ํ ์ ๋ฌ ๋ฌธ์ ํด๊ฒฐ
- ์ปดํฌ๋ํธ ์ค๊ณ: ์ํ ์์กด์ฑ์ ๊ณ ๋ คํ ์ปดํฌ๋ํธ ๊ตฌ์กฐ ์ค๊ณ
- ์ ์ง๋ณด์์ฑ ์ฐ์ : ์ฝ๋์ ๋ณต์ก์ฑ๋ณด๋ค ์ ์ง๋ณด์์ฑ๊ณผ ํ์ฅ์ฑ ๊ณ ๋ ค
- ์ํ ์ผ๊ด์ฑ: ์ ์ญ ์ํ๋ก ์ปดํฌ๋ํธ ๊ฐ ์ํ ๋๊ธฐํ ๋ณด์ฅ

