๋ฐ์ด๋ ๋ ์ฝ๋์ฒ๋ผ, ์์ ์ ์๊ธฐ๋ค
Spotify API๋ฅผ ํ์ฉํด ๋์ Top ํธ๋๊ณผ ์ถ์ฒ ์์
์ ํ์ํ๊ณ ,
๊ฐ์ฌ์ ์ํฐ์คํธ ์ ๋ณด๊น์ง ํ ํ๋ฉด์์ ์ฆ๊ธธ ์ ์๋ Spotify ์น ํด๋ผ์ด์ธํธ ํ๋ก์ ํธ.
ํด๋ผ์ด์ธํธ
-
React : ์ปดํฌ๋ํธ ๊ธฐ๋ฐ์ UI ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก, ํ๋ถํ ์ํ๊ณ์ ๋์ ์์จ์ฑ์ ๋ฐํ์ผ๋ก ๊ตฌ์กฐ์ ์ธ UI๋ฅผ ์ค๊ณํ๊ธฐ์ ์ ํฉํ๋ค๊ณ ํ๋จํ์ฌ ์ ํํ์ต๋๋ค.
-
TypeScript : ์ ์ ํ์ ๊ฒ์ฌ๋ฅผ ํตํ ์์ ์ฑ ํ๋ณด์, ํ์ ์ธํ ๋ฆฌ์ผ์ค๋ฅผ ํตํ ๊ฐ๋ฐ ์์ฐ์ฑ ํฅ์์ ์ํด ์ฌ์ฉํ์ต๋๋ค.
-
SASS -> Tailwind CSS : ๊ธฐ์กด์ ์ฌ์ฉํ๋ Sass ๋์ Tailwind CSS v4๋ฅผ ๋์ ํ์ต๋๋ค. @theme ๋ธ๋ก์์ ๋์์ธ ํ ํฐ์ CSS ๋ณ์๋ก ์ ์ํ๋ฉด ์ ํธ๋ฆฌํฐ ํด๋์ค๊ฐ ์๋ ์์ฑ๋์ด ๋ณ๋์ ๋ณ์ ํ์ผ ๊ด๋ฆฌ๊ฐ ํ์ ์์ด์ก๊ณ , ์คํ์ผ์ JSX์ ๊ฐ์ ํ์ผ์์ ์์ฑํ ์ ์์ด ์ปดํฌ๋ํธ๊ฐ ๋ง์ ๊ตฌ์กฐ์์ ํ์ผ ์ ํ ๋น์ฉ์ ์ค์์ต๋๋ค. ๋ํ ์ฌ์ฉํ ํด๋์ค๋ง ๋ฒ๋ค์ ํฌํจ๋๋ ํธ๋ฆฌ์์ดํน์ผ๋ก Sass ๋๋น CSS ๋ฒ๋ค ํฌ๊ธฐ๋ฅผ ์ต์ํํ์ต๋๋ค.
-
React Router DOM : SPA์์ ํ์ด์ง ์ ํ์ ๊ด๋ฆฌํ๊ธฐ ์ํด ์ฌ์ฉํ์ต๋๋ค. React.lazy์ ๊ฒฐํฉํด ํ์ด์ง ๋จ์์ ์ฝ๋ ์คํ๋ฆฌํ ์ ์ ์ธ์ ์ผ๋ก ๊ตฌํํ ์ ์์์ต๋๋ค.
๊ธฐ๋ฅ ๊ตฌํ์ ์ฌ์ฉํ ๋๊ตฌ
- ky : fetch API ๊ธฐ๋ฐ์ ๊ฒฝ๋ HTTP ํด๋ผ์ด์ธํธ๋ก, axios์ ๋นํด ๋ฒ๋ค ํฌ๊ธฐ๊ฐ ์๊ณ ์ธํฐ์ ํฐ ๊ตฌ์กฐ๊ฐ ๊ฐ๊ฒฐํ์ฌ ์ ํํ์ต๋๋ค. afterResponse ํ ์ ํ์ฉํด API ์ค๋ฅ๋ฅผ ์ค์์์ ์ผ๊ด๋๊ฒ ์ฒ๋ฆฌํ ์ ์์์ต๋๋ค.
- Zustand : ์ ์ญ ํ ์คํธ ์ํ ๊ด๋ฆฌ๋ฅผ ์ํด ์ฌ์ฉํ์ต๋๋ค. React Context ๋๋น ๋ณด์ผ๋ฌํ๋ ์ดํธ๊ฐ ์ ๊ณ , ์ปดํฌ๋ํธ ์ธ๋ถ์์๋ ์คํ ์ด์ ์ง์ ์ ๊ทผํ ์ ์์ด ํ ์คํธ ํฉํ ๋ฆฌ ํจํด๊ณผ ์์ฐ์ค๋ฝ๊ฒ ๊ฒฐํฉํ ์ ์์์ต๋๋ค. ํฉํ ๋ฆฌ ID๋ณ๋ก ํ์ฑ ํ ์คํธ๋ฅผ ์ถ์ ํ๋ฉฐ, dismissAllExcept๋ฅผ ํตํด ํน์ ํ๋ฉด ์ ํ ์ ์ปจํ ์คํธ ํ ์คํธ๋ง ์ ๊ฑฐํ๊ณ ์ ์ญ ํ ์คํธ๋ ์ ์งํ ์ ์๋๋ก ์ค๊ณํ์ต๋๋ค.
- TanStack Virtual : ๊ฒ์ ๊ฒฐ๊ณผ์ฒ๋ผ ์๋ฐฑ ๊ฐ์ ์์ดํ ์ด ํ ๋ฒ์ ๋ ๋๋ง๋ ์ ์๋ ๋ฆฌ์คํธ์์, DOM์ ์ค์ ๋ก ๋ณด์ด๋ ์์๋ง ๋ ๋๋งํ๋ ๊ฐ์ํ๋ฅผ ์ ์ฉํ๊ธฐ ์ํด ์ฌ์ฉํ์ต๋๋ค. ํยท์ด์ ๋ชจ๋ ๊ฐ์ํํ๋ 2D ๊ทธ๋ฆฌ๋ ๊ตฌํ์ ํ์ฉํ์ต๋๋ค.
- TanStack Query : ์๋ฒ ์ํ์ ํด๋ผ์ด์ธํธ ์ํ๋ฅผ ๋ช ํํ ๋ถ๋ฆฌํ์ฌ ๊ด๋ฆฌํ๊ธฐ ์ํด ์ฌ์ฉํ์ต๋๋ค. ์บ์ฑ, ์๋ ๋ฆฌํจ์นญ, ๋ก๋ฉยท์๋ฌ ์ํ ๊ด๋ฆฌ ๋ฑ์ ์ ์ธ์ ์ผ๋ก ์ฒ๋ฆฌํ ์ ์์ด, ์ง์ useEffect๋ก ๋น๋๊ธฐ ๋ก์ง์ ๋ค๋ฃจ๋ ๊ฒ๋ณด๋ค ํจ์ฌ ๊ฐ๊ฒฐํ๊ณ ์์ ์ ์ธ ์ฝ๋๋ฅผ ์์ฑํ ์ ์์์ต๋๋ค.
์๋ฌ ํธ๋ํน์ ์ฌ์ฉํ ๋๊ตฌ :
- Sentry : ํ๋ก๋์ ํ๊ฒฝ์์ ๋ฐ์ํ๋ ์๋ฌ๋ฅผ ์ค์๊ฐ์ผ๋ก ํธ๋ํนํ๊ธฐ ์ํด ์ฌ์ฉํ์ต๋๋ค. ๊ฐ๋ฐ ํ๊ฒฝ์์๋ ๋นํ์ฑํํ์ฌ ๋ ธ์ด์ฆ๋ฅผ ์ค์ด๊ณ , sourcemap์ ํ์ฉํด minified๋ ์ฝ๋์์๋ ์๋ฌ ๋ฐ์ ์์น๋ฅผ ์ ํํ ํ์ ํ ์ ์๋๋ก ํ์ต๋๋ค.
๋น๋ ๋๊ตฌ :
- Vite : ๋น ๋ฅธ HMR๊ณผ ES Module ๊ธฐ๋ฐ์ ๋ฒ๋ค๋ง์ ์ํด ์ฌ์ฉํ์ต๋๋ค. rollup-plugin-visualizer๋ฅผ ํตํด ๋น๋ ๊ฒฐ๊ณผ๋ฌผ์ ๋ฒ๋ค ์ฌ์ด์ฆ๋ฅผ ์๊ฐ์ ์ผ๋ก ๋ถ์ํ ์ ์์์ต๋๋ค.
๋ฐฐํฌ :
- Vercel : ๋ณ๋์ ์๋ฒ ์ค์ ์์ด GitHub ์ฐ๋๋ง์ผ๋ก CI/CD๋ฅผ ๊ตฌ์ฑํ ์ ์๊ณ , ํ๋ก ํธ์๋ ํ๋ก์ ํธ์ ์ต์ ํ๋ ๋ฐฐํฌ ํ๊ฒฝ์ ์ ๊ณตํ๊ธฐ ๋๋ฌธ์ ์ ํํ์ต๋๋ค.
| ๊ธฐ๋ฅ | ์คํฌ๋ฆฐ์ท |
|---|---|
| ๋ก๊ทธ์ธ | ![]() |
| ๋ฉ์ธ | ![]() |
| ํ์ฌ ์ฌ์ ๊ณก | ![]() |
| ์จ๋ฒ, ์ํฐ์คํธ, ํธ๋, ํ๋ ์ด๋ฆฌ์คํธ ๊ฒ์ |
API ํธ์ถ ๋ก์ง์ ์ปดํฌ๋ํธ์์ ์์ ํ ๋ถ๋ฆฌํ๊ณ , ๋๋ฉ์ธ(track / artist / search)๋ณ๋ก ์ฟผ๋ฆฌ ํ ์ ๊ตฌ์ฑํ์ต๋๋ค. ๋ชจ๋ ์ฟผ๋ฆฌ ํ ์ ๊ณตํต CONFIG๋ฅผ ์ฐธ์กฐํด ์ผ๊ด๋ retry / throwOnError ์ ์ฑ ์ ๋ฐ๋ฅด๋๋ก ์ค๊ณํ์ต๋๋ค.
retry ์ ์ฑ ์ ๊ฒฝ์ฐ 401, 403, 429 ์ํ๋ ์ฌ์๋ํ์ง ์๊ณ , 408(ํ์์์) ๋ฑ์ ์ผ์์ ์ค๋ฅ์ ๋ํด์๋ง ์ฌ์๋๋ฅผ ํ์ฉํ์ต๋๋ค. throwOnError ์ ์ฑ ์ ์ธ์ฆ ๊ด๋ จ ์ค๋ฅ(401, 403)์ ํํด์๋ง ์๋ฌ๋ฅผ ErrorBoundary๋ก ์ ํํ๋๋ก ์ค์ ํ์ฌ, ์ธ์ฆ ์ค๋ฅ์ผ ๋๋ ์ฌ์ฉ์๋ฅผ ๋ช ํํ ์๋ฌ ํ๋ฉด์ผ๋ก ์๋ดํ๊ณ , ๊ทธ ์ธ์ ์ค๋ฅ๋ ์กฐ์ฉํ ์ฒ๋ฆฌ๋๋๋ก ๊ตฌ๋ถํ์ต๋๋ค.
ky์ afterResponse ํ ์ ํ์ฉํด ๋ชจ๋ API ์๋ต์ ์ค๋ฅ๋ฅผ handleError ํจ์์์ ์ผ๊ด ์ฒ๋ฆฌํ๋๋ก ์ค๊ณํ์ต๋๋ค. ๊ฐ HTTP ์ํ ์ฝ๋์ ๋์ํ๋ ERROR_NAMES์ ERROR_MESSAGES๋ฅผ ์์๋ก ์ ์ํ๊ณ , ์ค๋ฅ ๊ฐ์ฒด์ name๊ณผ message๋ฅผ ํต์ผ๋ ํ์์ผ๋ก ๊ต์ฒดํจ์ผ๋ก์จ ์ปดํฌ๋ํธ ๊ณ์ธต์์๋ ์๋ฌ์ ์ถ์ฒ(Spotify API, OVH, ReccoBeats)์ ๊ด๊ณ์์ด ๋์ผํ ์ธํฐํ์ด์ค๋ก ์๋ฌ๋ฅผ ๋ค๋ฃฐ ์ ์๊ฒ ํ์ต๋๋ค.
401, 403 ์ค๋ฅ์ ๊ฒฝ์ฐ ๋ก์ปฌ์คํ ๋ฆฌ์ง์ ์ธ์ฆ ํ ํฐ์ ์ฆ์ ์ ๊ฑฐํ์ฌ ๋ง๋ฃ๋ ์ธ์ ์ด ๋จ์์์ง ์๋๋ก ์ฒ๋ฆฌํ์ต๋๋ค. ErrorBoundary์ FallbackRender์์๋ ๋์ผํ ERROR_MESSAGES ์์๋ฅผ ์ฐธ์กฐํ์ฌ, ์ธ์ฆ ์ค๋ฅ์ผ ๋๋ ๊ณ์ ์ค๋ฅ ํ๋ฉด์, ๊ทธ ์ธ์๋ ์ผ๋ฐ ์๋ฌ ํ๋ฉด์ ๋ ๋๋งํ๋๋ก ๋ถ๊ธฐํ์ต๋๋ค.
๊ฒ์ ๊ฒฐ๊ณผ ํ์ด์ง์์ ์๋ฐฑ ๊ฐ์ ์นด๋๊ฐ ๊ทธ๋ฆฌ๋ ํํ๋ก ๋ ๋๋ง๋ ๋, DOM์ ๊ณผ๋ถํ๊ฐ ๊ฑธ๋ฆฌ์ง ์๋๋ก TanStack Virtual์ ํยท์ด ๋์ ๊ฐ์ํ๋ฅผ ์ ์ฉํ์ต๋๋ค. useGridVirtualizer ํ ์์ ํ(row) virtualizer์ ์ด(column) virtualizer๋ฅผ ํจ๊ป ๊ตฌ์ฑํ๊ณ , ํ์ฌ ๋ทฐํฌํธ ๋๋น์ ๋ฐ๋ผ ์ปฌ๋ผ ์๋ฅผ ๋์ ์ผ๋ก ๊ณ์ฐํ๋๋ก ํ์ต๋๋ค.
๋ํ ๊ฐ์ํ๋ ํ์ ์ํํ๋ฉฐ ์ด๋ฏธ์ง URL์ ๋ฏธ๋ฆฌ ํ๋ฆฌ๋ก๋ํจ์ผ๋ก์จ, ์คํฌ๋กค ์ ์ด๋ฏธ์ง๊ฐ ๋ฆ๊ฒ ๋ํ๋๋ ํ์์ ์ค์์ต๋๋ค. ๋ฌดํ ์คํฌ๋กค์ ๋ง์ง๋ง์ผ๋ก ๋ณด์ด๋ ์์ดํ ์ ์ธ๋ฑ์ค์ ์ ์ฒด ์์ดํ ์๋ฅผ ๋น๊ตํด threshold ์ด๋ด๋ก ์ขํ์ก์ ๋ fetchNextPage๋ฅผ ํธ์ถํ๋ ๋ฐฉ์์ผ๋ก ๊ตฌํํ์ต๋๋ค.
React.lazy์ Suspense๋ฅผ ํ์ฉํด ํ์ด์ง ๋จ์์ ์ฃผ์ ์ปดํฌ๋ํธ ๋จ์๋ก ์ฝ๋ ์คํ๋ฆฌํ ์ ์ ์ฉํ์ต๋๋ค. ์ด๊ธฐ ๋ฒ๋ค์ ํฌํจ๋ ํ์๊ฐ ์๋ ์ปดํฌ๋ํธ๋ ์ฌ์ฉ ์์ ์ ๋์ ์ผ๋ก ๋ก๋๋๋๋ก ํ์ฌ, ์ด๊ธฐ ๋ก๋ฉ ์๊ฐ์ ์ค์ด๊ณ ์ ํ์ต๋๋ค. Skeleton ์ปดํฌ๋ํธ๋ฅผ fallback์ผ๋ก ์ ๊ณตํด ๋ก๋ฉ ์ค์๋ ๋ ์ด์์์ด ์์ ์ ์ผ๋ก ์ ์ง๋๋๋ก ํ์ต๋๋ค.
atomic ํจํด ๋์ ๋๋ฉ์ธ ์ค์ฌ์ ํด๋ ๊ตฌ์กฐ๋ฅผ ์ ํํ์ต๋๋ค. ๊ธฐ๋ฅ๊ณผ ๋๋ฉ์ธ ๋จ์๋ก ํ์ผ์ ๋ฌถ์ผ๋ฉด ํน์ ์ปดํฌ๋ํธ๋ฅผ ์ฐพ์ ๋ ๊ณ์ธต์ ๋ถ๋ฅ๋ฅผ ๋จผ์ ๋ ์ฌ๋ ค์ผ ํ๋ ์ธ์ง ๋ถํ๊ฐ ์ค์ด๋ค๊ณ , ๊ด๋ จ ํ์ผ๋ผ๋ฆฌ์ ์์ง๋๊ฐ ๋์์ง๋ค๊ณ ํ๋จํ๊ธฐ ๋๋ฌธ์ ๋๋ค. ๋ฒ์ฉ UI ์์๋ ui ํด๋์, ๋๋ฉ์ธ๋ณ ์ปดํฌ๋ํธ๋ components ํ์์ ๋๋ฉ์ธ ์ด๋ฆ์ผ๋ก ๊ตฌ๋ถํ์ต๋๋ค.
@/* ๋ก src ๋๋ ํฐ๋ฆฌ๋ฅผ ์ฐธ์กฐํ๋ ์ ๋ ๊ฒฝ๋ก ์ํฌํธ๋ฅผ ์ฌ์ฉํ์ต๋๋ค. ํ์ผ์ด ์ด๋ํ๊ฑฐ๋ ํด๋ ๊ตฌ์กฐ๊ฐ ๋ณ๊ฒฝ๋ ๋ ์๋ ๊ฒฝ๋ก(../../../)๋ฅผ ์ผ์ผ์ด ์์ ํ์ง ์์๋ ๋์ด ์ ์ง๋ณด์ ๋น์ฉ์ ๋ฎ์ถ ์ ์์์ต๋๋ค.
Husky์ commitlint๋ฅผ ์ฐ๋ํด ์ปค๋ฐ ๋ฉ์์ง๊ฐ Conventional Commits ๊ท๊ฒฉ์ ๋ฐ๋ฅด์ง ์์ผ๋ฉด ์ปค๋ฐ์ด ๊ฑฐ๋ถ๋๋๋ก ํ์ต๋๋ค. ์ผ๊ด๋ ์ปค๋ฐ ํ์คํ ๋ฆฌ๋ฅผ ์ ์งํจ์ผ๋ก์จ ๋ณ๊ฒฝ ์ฌํญ์ ๋งฅ๋ฝ์ ์ฝ๊ฒ ํ์ ํ ์ ์์์ต๋๋ค.
clamp() ยท max() ๊ธฐ๋ฐ์ ์ ๋์ CSS ์ปค์คํ
ํ๋กํผํฐ๋ฅผ ์ ์ํด ๋ทฐํฌํธ์ ๋ฐ๋ผ ํ
์คํธ ํฌ๊ธฐยท์ด๋ฏธ์ง ํฌ๊ธฐยท์ฌ๋ฐฑยท๋ณด๋ ๋ฐ๊ฒฝ์ด ์๋์ผ๋ก ์กฐ์ ๋๋๋ก ํ์ต๋๋ค. ๊ณ ์ ๋ธ๋ ์ดํฌํฌ์ธํธ ๋์ fluid ๊ฐ์ ์ฌ์ฉํจ์ผ๋ก์จ ์ค๊ฐ ๋ทฐํฌํธ์์๋ ๋ ์ด์์์ด ์์ฐ์ค๋ฝ๊ฒ ํ๋ฅด๋๋ก ํ์ต๋๋ค.
/* fluid typography */
--text-fluid-xl: clamp(var(--text-base), 5vw, var(--text-4xl));
--text-fluid-xs: clamp(0.3rem, 3vw, var(--text-xs));
/* fluid vinyl album cover */
--vinyl-album-fluid: clamp(9rem, 26rem, 28vw);๊ฐ์ํ ๊ทธ๋ฆฌ๋(useGridVirtualizer)๋ useThrottledWindowSize๋ก ํ์ฌ ๋ทฐํฌํธ ๋๋น๋ฅผ ๊ฐ์งํด ์ปฌ๋ผ ์๋ฅผ ๋์ ์ผ๋ก ๊ณ์ฐํฉ๋๋ค. Tailwind์ sm: ยท md: ยท lg: ๋ฐ์ํ prefix๋ฅผ ๊ทธ๋ฆฌ๋์ ๋ ์ด์์ ์ปดํฌ๋ํธ ์ ๋ฐ์ ์ ์ฉํ์ต๋๋ค.
- ํค๋ณด๋ ํ์ : ๋ชจ๋ ๋ฒํผ๊ณผ ํญ์
focus-visibleํฌ์ปค์ค ์ธ๋์ผ์ดํฐ๋ฅผ ์ ์ฉํด ๋ง์ฐ์ค ์์ด๋ ํ์ ๊ฐ๋ฅํ๋๋ก ํ์ต๋๋ค. - ์คํฌ๋ฆฐ ๋ฆฌ๋ ์ง์ : ์์ด์ฝ ์ ์ฉ ๋ฒํผ์๋
aria-label๋ก ๋ชฉ์ ์ ์ค๋ช ํ๊ณ , ์ฅ์์ฉ SVG์๋aria-hidden+focusable="false"๋ฅผ ์ ์ฉํ์ต๋๋ค. ๋ด๋น๊ฒ์ด์ ๋ฒํผ์๋ ํ์ฌ ํ์ด์ง ์ฌ๋ถ๋ฅผaria-current="page"๋ก ์ ๋ฌํฉ๋๋ค. - ๋์ ์ฝํ
์ธ ์๋ฆผ : ๊ฒ์ ๋ก๋ฉ ์ํ๋ฅผ
aria-live="polite"๋ก ์ ์ธํด ์ํ ๋ณํ๋ฅผ ์คํฌ๋ฆฐ ๋ฆฌ๋์ ์๋ฆฝ๋๋ค. - ํญ ํจํด : ๊ฒ์ ์นดํ
๊ณ ๋ฆฌ ํญ์
role="tablist"ยทrole="tab"ยทaria-selected๋ฅผ ์ ์ฉํด WAI-ARIA ํญ ํจํด์ ์ค์ํ์ต๋๋ค. - ๋ชจ์
๊ฐ์ ์ค์ ์กด์ค :
@media (prefers-reduced-motion: reduce)๋ก ๋ฐ์ด๋ ํ์ ยท์บ๋ฌ์ ยท๋ค์จ ์ ๋๋ฉ์ด์ ์ ๋นํ์ฑํํด ์ ์ ์ฅ์ ๊ฐ ์๋ ์ฌ์ฉ์๋ฅผ ๋ฐฐ๋ คํ์ต๋๋ค.





