[๐ค] React Suspense์ ErrorBoundary: ๊ฒฌ๊ณ ํ๊ณ ๋ถ๋๋ฌ์ด UI ๊ฒฝํ์ ์ํ ์ค์ ๊ฐ์ด๋
React ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ฌ์ฉ์ ๊ฒฝํ์ ํ์ ํ Suspense์ ErrorBoundary์ ๊ฐ๋ ฅํ ์กฐํฉ์ ๊น์ด ์๊ฒ ๋ค๋ค์. ๋ก๋ฉ ์ํ์ ์๋ฌ ์ฒ๋ฆฌ๋ฅผ ์ฐ์ํ๊ฒ ๊ด๋ฆฌํ์ฌ ๋์ฑ ๊ฒฌ๊ณ ํ๊ณ ๋ถ๋๋ฌ์ด UI๋ฅผ ๋ง๋๋ ์ค์ ํ๊ณผ ์ฝ๋ ์์๋ฅผ ํ์ธํด ๋ณด์ธ์.
์ ๋ณด๐ค ์ด ํฌ์คํ ์ Gemini 2.5 Flash AI๊ฐ ์์ฑํ์ด์.
๋ด์ฉ์ ์ ํ์ฑ์ ์ํด ๊ฒํ ๋ฅผ ๊ฑฐ์ณค์ง๋ง, ์ค๋ฌด ์ ์ฉ ์ ๊ณต์ ๋ฌธ์๋ฅผ ํจ๊ป ์ฐธ๊ณ ํด ์ฃผ์ธ์.
์ ์ฉํ ํ์ด ๊ธ์์๋ React ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ฌ์ฉ์ ๊ฒฝํ์ ์ ํดํ๋ ๋ก๋ฉ๊ณผ ์๋ฌ ์ํฉ์ ํจ๊ณผ์ ์ผ๋ก ๊ด๋ฆฌํ๊ธฐ ์ํด Suspense์ ErrorBoundary๋ฅผ ์ด๋ป๊ฒ ์กฐํฉํ์ฌ ์ฌ์ฉํ๋์ง, ๊ทธ๋ฆฌ๊ณ ์ด๋ค์ ํตํด ์ด๋ป๊ฒ ๋์ฑ ๊ฒฌ๊ณ ํ๊ณ ๋ถ๋๋ฌ์ด UI๋ฅผ ๊ตฌ์ถํ ์ ์๋์ง ์ค์ ์ฝ๋์ ํจ๊ป ์์ธํ ์์๋ณผ ๊ฑฐ์์.
์๋
ํ์ธ์, 10๋
์ด์ ํ๋ก ํธ์๋์ ๋ฐฑ์๋๋ฅผ ๋๋๋ค๋ฉฐ ๊ฐ๋ฐ ํ์ฅ์์ ๊ตฌ๋ฅธ ์๋์ด ํ์คํ ๊ฐ๋ฐ์ ๋ธ๋ฃจ์์. ์ค๋์ React ์ ํ๋ฆฌ์ผ์ด์
์ ์ฌ์ฉ์ ๊ฒฝํ(UX)์ ํ ๋จ๊ณ ๋์ด์ฌ๋ฆด ์ ์๋ ๋ ๊ฐ์ง ๊ฐ๋ ฅํ ๊ธฐ๋ฅ, ๋ฐ๋ก Suspense์ ErrorBoundary์ ๋ํด ์ด์ผ๊ธฐํด ๋ณด๋ ค๊ณ ํด์.
๋ง์ ๊ฐ๋ฐ์๋ถ๋ค์ด ๋ก๋ฉ ์คํผ๋์ ํ์ ์์์ ํค๋งค๊ฑฐ๋, ์์์น ๋ชปํ ์๋ฌ๋ก ์ธํด ์ฌ์ฉ์์๊ฒ ํ ๋น ํ๋ฉด์ ๋ณด์ฌ์ฃผ๋ ์ํฉ์ ๋ต๋ตํจ์ ๋๋ผ์ จ์ ๊ฑฐ์์. ์ด ๋ ๊ฐ์ง React์ ํต์ฌ ๊ธฐ๋ฅ์ ์ ํ์ฉํ๋ฉด ์ด๋ฐ ๋ฌธ์ ๋ค์ ์ฐ์ํ๊ฒ ํด๊ฒฐํ๊ณ , ์ฌ์ฉ์์๊ฒ ํจ์ฌ ๋ ๋ถ๋๋ฝ๊ณ ์์ ์ ์ธ ๊ฒฝํ์ ์ ๊ณตํ ์ ์๋ต๋๋ค.
๐ค ๋ฌธ์ /๋ฐฐ๊ฒฝ: ๋ก๋ฉ๊ณผ ์๋ฌ, ์ฌ์ฉ์ ๊ฒฝํ์ ํด์น๋ ์ฃผ๋ฒ
์ฐ๋ฆฌ๊ฐ ๋ง๋๋ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ํ์ฐ์ ์ผ๋ก ๋คํธ์ํฌ ์์ฒญ๊ณผ ๋น๋๊ธฐ ๋ฐ์ดํฐ ์ฒ๋ฆฌ๋ฅผ ์๋ฐํด์. ๊ทธ๋ฆฌ๊ณ ๊ทธ ๊ณผ์ ์์ ์๊ธฐ์น ์์ ์๋ฌ๊ฐ ๋ฐ์ํ๊ธฐ๋ ํ์ฃ . ์ด๋ฐ ์ํฉ๋ค์ ์ ๋๋ก ์ฒ๋ฆฌํ์ง ๋ชปํ๋ฉด ์ฌ์ฉ์ ๊ฒฝํ์ ํฌ๊ฒ ์ ํ๋ ์ ์์ด์.
0๏ธโฃ ๊ธฐ์กด ๋ก๋ฉ ์ฒ๋ฆฌ์ ํ๊ณ: โ์คํผ๋ ์ง์ฅโ๊ณผ ๋ณต์กํ ์ํ ๊ด๋ฆฌ
๊ธฐ์กด์๋ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ฌ ๋ isLoading๊ณผ ๊ฐ์ ์ํ ๋ณ์๋ฅผ ์ปดํฌ๋ํธ๋ง๋ค ๊ด๋ฆฌํ๋ฉฐ ๋ก๋ฉ ์คํผ๋๋ฅผ ๋ณด์ฌ์ฃผ๋ ๊ฒ์ด ์ผ๋ฐ์ ์ด์์ด์. ํ์ง๋ง ์ด ๋ฐฉ์์ ๋ช ๊ฐ์ง ํ๊ณ๋ฅผ ๊ฐ์ง๊ณ ์์ด์.
- ์คํผ๋ ์ง์ฅ (Spinner Hell): ์ฌ๋ฌ ์ปดํฌ๋ํธ์์ ๋์์ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ฌ ๋, ๊ฐ๊ธฐ ๋ค๋ฅธ ์คํผ๋๊ฐ ๋์ง๋์ง ๋ถ์ด ํ๋ฉด์ด ์ง์ ๋ถํด ๋ณด์ผ ์ ์์ด์.
- ๋ก๋ฉ ์ํ ๊ด๋ฆฌ์ ๋ณต์ก์ฑ:
isLoading์ํ๋ฅผ ๋ถ๋ชจ ์ปดํฌ๋ํธ์์ ์์ ์ปดํฌ๋ํธ๋ก ์ ๋ฌํ๊ฑฐ๋, ์ ์ญ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํตํด ๊ด๋ฆฌํด์ผ ํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ ์ฝ๋๊ฐ ๋ณต์กํด์ง๊ณ ์ ์ง๋ณด์๊ฐ ์ด๋ ค์์ ธ์. - ๋ฐ์ดํฐ ํ์นญ ๋ก์ง ๋ถ์ฐ: ๋ฐ์ดํฐ ํ์นญ ๋ก์ง์ด
useEffect์์ ์๊ฑฐ๋, ๊ฐ ์ปดํฌ๋ํธ์ ๋ผ์ดํ์ฌ์ดํด๊ณผ ์ฎ์ฌ ์์ด ์ฌ์ฌ์ฉ์ฑ๊ณผ ์์ง์ฑ์ด ๋จ์ด์ง๊ธฐ ์ฌ์์.
1๏ธโฃ ๊ธฐ์กด ์๋ฌ ์ฒ๋ฆฌ์ ํ๊ณ: ํ๋ฉด ์ ์ฒด ํฌ๋์์ ์ผ๊ด์ฑ ์๋ UI
JavaScript ์ ํ๋ฆฌ์ผ์ด์
์์ ์๋ฌ๋ ์ธ์ ๋ ๋ฐ์ํ ์ ์๋ ์ผ์ด์์. ํนํ React ์ปดํฌ๋ํธ ๋ ๋๋ง ๊ณผ์ ์์ ๋ฐ์ํ๋ ์๋ฌ๋ ์ ํ๋ฆฌ์ผ์ด์
์ ์ฒด๋ฅผ ๋ฉ์ถ๊ฒ ๋ง๋ค ์ ์์ด์.
- ํ๋ฉด ์ ์ฒด ํฌ๋์: ์์ ์ปดํฌ๋ํธ์์ ๋ฐ์ํ ๋ ๋๋ง ์๋ฌ๊ฐ ๋ถ๋ชจ ์ปดํฌ๋ํธ๋ก ์ ํ๋์ด ์ ํ๋ฆฌ์ผ์ด์
์ ์ฒด๊ฐ ๋ฉ์ถ๊ณ ๋น ํ๋ฉด์ ๋ณด์ฌ์ฃผ๋ ๊ฒฝ์ฐ๊ฐ ํํ์ด์.
- ์ผ๊ด์ฑ ์๋ ์๋ฌ UI: ๊ฐ ์ปดํฌ๋ํธ์์
try-catch๋ฑ์ผ๋ก ์๋ฌ๋ฅผ ๊ฐ๋ณ์ ์ผ๋ก ์ฒ๋ฆฌํ๋ค ๋ณด๋ฉด, ์๋ฌ ๋ฉ์์ง๋ UI ์คํ์ผ์ด ์ผ๊ด๋์ง ์์ ์ฌ์ฉ์์๊ฒ ํผ๋์ ์ค ์ ์์ด์. - ์๋ฌ ๋ก๊น ์ ์ด๋ ค์: ์๋ฌ๊ฐ ๋ฐ์ํ์ ๋ ์ด๋ฅผ ์ค์์์ ์์งํ๊ณ ๋ก๊น ํ๋ ๋ฉ์ปค๋์ฆ์ด ์์ผ๋ฉด ๋ฌธ์ ์ง๋จ๊ณผ ํด๊ฒฐ์ด ์ด๋ ค์์ ธ์.
โ๏ธ ํด๊ฒฐ ๋ฐฉ๋ฒ: Suspense์ ErrorBoundary์ ์๋์ง
React๋ ์ด๋ฌํ ๋ฌธ์ ๋ค์ ํด๊ฒฐํ๊ธฐ ์ํด Suspense์ ErrorBoundary๋ผ๋ ๊ฐ๋ ฅํ ๋๊ตฌ๋ฅผ ์ ๊ณตํด์. ์ด ๋์ ํจ๊ป ์ฌ์ฉ๋ ๋ ์ง์ ํ ์๋์ง๋ฅผ ๋ฐํํ๋ฉฐ, ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ฒฌ๊ณ ์ฑ๊ณผ ์ฌ์ฉ์ ๊ฒฝํ์ ํฌ๊ฒ ํฅ์์ํฌ ์ ์๋ต๋๋ค.
0๏ธโฃ React Suspense๋ก ๋น๋๊ธฐ ๋ฐ์ดํฐ ํ์นญ๊ณผ ๋ก๋ฉ UI ํตํฉํ๊ธฐ
React Suspense๋ ์ปดํฌ๋ํธ๊ฐ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ค๋ ๋์ ๋ ๋๋ง์ ์ผ์ ์ค์งํ๊ณ , ๋ฐ์ดํฐ ์ค๋น๊ฐ ์๋ฃ๋ ๋๊น์ง ์ง์ ๋ fallback UI๋ฅผ ๋ณด์ฌ์ฃผ๋ ๋ฉ์ปค๋์ฆ์ด์์. React 18๋ถํฐ๋ React.lazy์ ํจ๊ป ์ฝ๋ ์คํ๋ฆฌํ
๋ฟ๋ง ์๋๋ผ ๋ฐ์ดํฐ ํ์นญ์๋ ์ ์์ผ๋ก ํ์ฉ๋ ์ ์๊ฒ ๋์์ด์.
์ ๋ณด์ด๋ป๊ฒ ๋์ํ๋์?
Suspense๋ ์ปดํฌ๋ํธ ๋ด๋ถ์์ ๋น๋๊ธฐ ์์ (๋ฐ์ดํฐ ํ์นญ Promise)์ด ๋ฐ์ํ์ฌ ๋ ๋๋ง์ โ์ผ์ ์ค๋จโ(suspend)์ํค๋ฉด, ๊ฐ์ฅ ๊ฐ๊น์ด ๋ถ๋ชจ Suspense ์ปดํฌ๋ํธ๊ฐ ๊ทธ ์์ ์ ๊ฐ์งํ๊ณfallback์ผ๋ก ์ง์ ๋ UI๋ฅผ ๋์ ๋ ๋๋งํด์. Promise๊ฐ ํด๊ฒฐ๋๋ฉด ๋ค์ Suspense ๋ด๋ถ์ ์ปดํฌ๋ํธ ๋ ๋๋ง์ ์ฌ๊ฐํ๋ต๋๋ค.
์ฃผ์ ์ด์ :
- ์ ์ธ์ ์ธ ๋ก๋ฉ ์ํ ๊ด๋ฆฌ:
isLoading์ํ๋ฅผ ์ง์ ๊ด๋ฆฌํ ํ์ ์์ด,Suspense๋ก ๊ฐ์ธ๊ธฐ๋ง ํ๋ฉด ๋น๋๊ธฐ ์์ ์ด ๋๋ ๋๊น์ง ์๋์ผ๋กfallbackUI๋ฅผ ๋ณด์ฌ์ค์. - ๋ก๋ฉ UI์ ์ค์ ์ง์คํ: ์ฌ๋ฌ ์ปดํฌ๋ํธ์ ๋ก๋ฉ ์ํ๋ฅผ ํ ๊ณณ์์ ๊ด๋ฆฌํ ์ ์์ด '์คํผ๋ ์ง์ฅ'์ ๋ฐฉ์งํ๊ณ ์ผ๊ด๋ ์ฌ์ฉ์ ๊ฒฝํ์ ์ ๊ณตํ ์ ์์ด์.
- ๋ฐ์ดํฐ ํ์นญ ๋ก์ง ๋ถ๋ฆฌ: ์ปดํฌ๋ํธ ๋ ๋๋ง ๋ก์ง๊ณผ ๋ฐ์ดํฐ ํ์นญ ๋ก์ง์ ๋์ฑ ๋ช
ํํ๊ฒ ๋ถ๋ฆฌํ ์ ์๊ฒ ๋์์ค์.
react-query,swr๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ด Suspense๋ฅผ ์ง์ํ๋ฉฐ ๋์ฑ ํธ๋ฆฌํ ๋ฐ์ดํฐ ํ์นญ ๊ฒฝํ์ ์ ๊ณตํ๊ณ ์์ด์.
1๏ธโฃ React ErrorBoundary๋ก ์ปดํฌ๋ํธ ์๋ฌ ๊ฒฉ๋ฆฌํ๊ธฐ
ErrorBoundary๋ React ์ปดํฌ๋ํธ ํธ๋ฆฌ์์ ์์ ์ปดํฌ๋ํธ๋ค์ด ๋์ง JavaScript ์๋ฌ๋ฅผ ์ก์๋ด๊ณ , ์๋ฌ๊ฐ ๋ฐ์ํ์ ๋ ์ง์ ๋ fallback UI๋ฅผ ๋ ๋๋งํ๋ React ์ปดํฌ๋ํธ์์. ๋ง์น try-catch ๋ธ๋ก์ด JavaScript ์ฝ๋์์ ์๋ฌ๋ฅผ ์ก๋ ๊ฒ์ฒ๋ผ, ErrorBoundary๋ React ์ปดํฌ๋ํธ ํธ๋ฆฌ์์ ์๋ฌ๋ฅผ ์ก๋ ์ญํ ์ ํด์.
๊ฒฝ๊ณ์ฃผ์ํ ์ : ErrorBoundary๋ ๋ ๋๋ง ๋จ๊ณ์์ ๋ฐ์ํ๋ ์๋ฌ(์:
render๋ฉ์๋ ์์์ ์๋ฌ ๋ฐ์)๋ง ์ก์๋ผ ์ ์์ด์. ์ด๋ฒคํธ ํธ๋ค๋ฌ ๋ด๋ถ๋ ๋น๋๊ธฐ ์ฝ๋(Promisecatch๋ธ๋ก ์ธ๋ถ)์์ ๋ฐ์ํ๋ ์๋ฌ๋ ์ก์๋ด์ง ๋ชปํด์. ์ด๋ฌํ ์๋ฌ๋ ์ผ๋ฐ์ ์ธtry-catch๋window.onerror๋ฑ์ ์ ์ญ ์๋ฌ ํธ๋ค๋ง์ผ๋ก ์ฒ๋ฆฌํด์ผ ํ๋ต๋๋ค.
์ฃผ์ ์ด์ :
- ๋ถ๋ถ์ ์ธ ์๋ฌ ์ฒ๋ฆฌ: ์์ ์ปดํฌ๋ํธ์์ ์๋ฌ๊ฐ ๋ฐ์ํด๋ ์ ํ๋ฆฌ์ผ์ด์
์ ์ฒด๊ฐ ๋ฉ์ถ์ง ์๊ณ , ์๋ฌ๊ฐ ๋ฐ์ํ ๋ถ๋ถ๋ง
fallbackUI๋ก ๋์ฒดํ ์ ์์ด์. - ์ผ๊ด๋ ์๋ฌ UI: ๋ชจ๋ ์๋ฌ๋ฅผ ErrorBoundary๋ฅผ ํตํด ์ฒ๋ฆฌํจ์ผ๋ก์จ ์ ํ๋ฆฌ์ผ์ด์
์ ๋ฐ์ ๊ฑธ์ณ ์ผ๊ด๋ ์๋ฌ ๋ฉ์์ง์ ๋์์ธ์ ์ ์งํ ์ ์์ด์.
- ์๋ฌ ๋ก๊น
:
componentDidCatch๋ผ์ดํ์ฌ์ดํด ๋ฉ์๋๋ฅผ ํตํด ์๋ฌ ์ ๋ณด๋ฅผ ์์งํ๊ณ Sentry ๊ฐ์ ์๋ฌ ๋ก๊น ์๋น์ค๋ก ์ ์กํ์ฌ ๋ฌธ์ ์ง๋จ์ ํ์ฉํ ์ ์์ด์.
๐งช ์ค์ ์์: ๋ฐ์ดํฐ ํ์นญ๊ณผ ์๋ฌ ์ฒ๋ฆฌ์ ์ฐ์ํ ๊ตฌํ
์ด์ Suspense์ ErrorBoundary๋ฅผ ์ค์ ๋ก ์ด๋ป๊ฒ ๊ตฌํํ๊ณ ํ์ฉํ๋์ง ์ฝ๋ ์์๋ฅผ ํตํด ์ดํด๋ณผ๊น์? ์ฌ๊ธฐ์๋ ๊ฐ๋จํ ๋ฐ์ดํฐ ํ์นญ ์ํฉ๊ณผ ์๋ฌ ๋ฐ์ ์ํฉ์ ๊ฐ์ ํด๋ณผ๊ฒ์.
0๏ธโฃ Suspense๋ฅผ ํ์ฉํ ๋น๋๊ธฐ ๋ฐ์ดํฐ ๋ก๋ฉ
Suspense์ ํจ๊ป ์ฌ์ฉํ๊ธฐ ์ํด์๋ Promise๋ฅผ ๋์ง ์ ์๋ ๋ฐ์ดํฐ ํ์นญ ๋ก์ง์ด ํ์ํด์. react-query๋ swr ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ฉด ์ด ๋ถ๋ถ์ด ํจ์ฌ ํธ๋ฆฌํ์ง๋ง, ์ฌ๊ธฐ์๋ ๊ฐ๋
์ดํด๋ฅผ ๋๊ธฐ ์ํด ๊ฐ๋จํ ํฌํผ ํจ์๋ฅผ ๋ง๋ค์ด๋ณผ๊ฒ์.
// utils/createSuspenseResource.ts interface Resource<T> { read(): T; } function createSuspenseResource<T>(promise: Promise<T>): Resource<T> { let status = "pending"; let result: T | Error; const suspender = promise.then( (r) => { status = "success"; result = r; }, (e) => { status = "error"; result = e; } ); return { read(): T { if (status === "pending") { throw suspender; // Promise๋ฅผ ๋์ ธ Suspense๊ฐ ์ก๋๋ก ํจ } if (status === "error") { throw result; // ErrorBoundary๊ฐ ์ก๋๋ก ์๋ฌ๋ฅผ ๋์ง } return result as T; }, }; } // ๊ฐ์์ ๋ฐ์ดํฐ ํ์นญ ํจ์ const fetchUserData = () => new Promise<{ id: number; name: string; email: string; }>((resolve) => { setTimeout(() => { resolve({ id: 1, name: "๋ธ๋ฃจ", email: "blue@example.com", }); }, 2000); }); // ๋ฐ์ดํฐ๋ฅผ Suspense Resource๋ก ๋ํ export const userResource = createSuspenseResource(fetchUserData()); // components/UserProfile.tsx import React from "react"; import { userResource } from "../utils/createSuspenseResource"; function UserProfile() { const user = userResource.read(); // ๋ฐ์ดํฐ๊ฐ ์ค๋น๋ ๋๊น์ง Suspense๊ฐ ๊ธฐ๋ค๋ฆผ return ( <div className="user-profile"> <h3>โจ ์ฌ์ฉ์ ํ๋กํ</h3> <p>์ด๋ฆ: {user.name}</p> <p>์ด๋ฉ์ผ: {user.email}</p> </div> ); } export default UserProfile; // components/LoadingSpinner.tsx import React from "react"; function LoadingSpinner() { return ( <div className="loading-spinner"> <p>โณ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ค๋ ์ค์ด์์...</p> </div> ); } export default LoadingSpinner; // App.tsx ๋๋ ๋ถ๋ชจ ์ปดํฌ๋ํธ import React, { Suspense } from "react"; import UserProfile from "./components/UserProfile"; import LoadingSpinner from "./components/LoadingSpinner"; function App() { return ( <div className="app"> <h1>๐ Suspense ์์</h1> <Suspense fallback={<LoadingSpinner />}> <UserProfile /> </Suspense> </div> ); } export default App;
์ ์ฝ๋์์ UserProfile ์ปดํฌ๋ํธ๋ userResource.read()๋ฅผ ํธ์ถํ๊ณ , ๋ฐ์ดํฐ๊ฐ ์ค๋น๋์ง ์์๋ค๋ฉด createSuspenseResource ๋ด๋ถ์์ ๋์ ธ์ง Promise๋ฅผ ๊ฐ์ฅ ๊ฐ๊น์ด Suspense ์ปดํฌ๋ํธ๊ฐ ์ก์ LoadingSpinner๋ฅผ ๋ณด์ฌ์ฃผ๊ฒ ๋ผ์. ๋ฐ์ดํฐ ๋ก๋ฉ์ด ์๋ฃ๋๋ฉด UserProfile์ด ๋ ๋๋ง๋๋ต๋๋ค.
1๏ธโฃ ErrorBoundary ์ปดํฌ๋ํธ ๊ตฌํ
ErrorBoundary๋ ํด๋์ค ์ปดํฌ๋ํธ๋ก ๊ตฌํํด์ผ ํ๋ฉฐ, static getDerivedStateFromError์ componentDidCatch ๋ผ์ดํ์ฌ์ดํด ๋ฉ์๋๋ฅผ ํฌํจํด์ผ ํด์.
// components/ErrorBoundary.tsx import React, { Component, ErrorInfo, ReactNode } from "react"; interface Props { children: ReactNode; fallback: ReactNode; } interface State { hasError: boolean; } class ErrorBoundary extends Component<Props, State> { public state: State = { hasError: false, }; // ์๋ฌ๊ฐ ๋ฐ์ํ์ ๋ ํธ์ถ๋๋ฉฐ, ๋ค์ ๋ ๋๋ง์์ fallback UI๋ฅผ ๋ณด์ฌ์ฃผ๊ธฐ ์ํด // ์ํ๋ฅผ ์ ๋ฐ์ดํธํด์. public static getDerivedStateFromError(_: Error): State { return { hasError: true }; } // ์๋ฌ์ ์๋ฌ ์ ๋ณด๋ฅผ ๋ก๊น ํ ์ ์์ด์. public componentDidCatch(error: Error, errorInfo: ErrorInfo) { console.error("๐จ Error caught by ErrorBoundary:", error, errorInfo); // ์ค์ ํ๋ก๋์ ์์๋ Sentry์ ๊ฐ์ ์๋ฌ ๋ก๊น ์๋น์ค๋ก ์ ์กํด์. // logErrorToService(error, errorInfo); } public render() { if (this.state.hasError) { // fallback UI๋ฅผ ๋ ๋๋งํด์. return this.props.fallback; } return this.props.children; } } export default ErrorBoundary; // components/BuggyComponent.tsx import React, { useState } from "react"; function BuggyComponent() { const [count, setCount] = useState(0); const handleClick = () => { setCount((prev) => prev + 1); }; if (count > 3) { // ๋ ๋๋ง ๊ณผ์ ์์ ์๋ฌ๋ฅผ ๊ฐ์ ๋ก ๋ฐ์์์ผ์. throw new Error("๐ฅ ์๋์ ์ผ๋ก ๋ฐ์์ํจ ์๋ฌ์์!"); } return ( <div className="buggy-component"> <p>์นด์ดํธ: {count}</p> <button onClick={handleClick}>์นด์ดํธ ์ฆ๊ฐ</button> <p>์นด์ดํธ๊ฐ 3์ ์ด๊ณผํ๋ฉด ์๋ฌ๊ฐ ๋ฐ์ํด์.</p> </div> ); } export default BuggyComponent; // components/ErrorFallback.tsx import React from "react"; function ErrorFallback({ error, resetErrorBoundary, }: { error?: Error; resetErrorBoundary?: () => void; }) { return ( <div role="alert" className="error-fallback"> <h3>โ ๏ธ ๋ฌด์ธ๊ฐ ์๋ชป๋์์ด์!</h3> <p>์๋ฌ ๋ฉ์์ง: {error?.message}</p> {resetErrorBoundary && ( <button onClick={resetErrorBoundary}>๋ค์ ์๋</button> )} </div> ); } export default ErrorFallback; // App.tsx ๋๋ ๋ถ๋ชจ ์ปดํฌ๋ํธ import React from "react"; import ErrorBoundary from "./components/ErrorBoundary"; import BuggyComponent from "./components/BuggyComponent"; import ErrorFallback from "./components/ErrorFallback"; function App() { return ( <div className="app"> <h1>๐ฅ ErrorBoundary ์์</h1> <ErrorBoundary fallback={<ErrorFallback />}> <BuggyComponent /> </ErrorBoundary> </div> ); } export default App;
BuggyComponent๋ count๊ฐ 3์ ์ด๊ณผํ๋ฉด ์๋ฌ๋ฅผ ๋์ง๋๋ก ๋ง๋ค์์ด์. ์ด ์๋ฌ๋ ๊ฐ์ฅ ๊ฐ๊น์ด ErrorBoundary๊ฐ ์ก์๋ด์ด ErrorFallback ์ปดํฌ๋ํธ๊ฐ ๋์ ๋ ๋๋ง๋๋ต๋๋ค. ์ ํ๋ฆฌ์ผ์ด์
์ ์ฒด๊ฐ ๋ฉ์ถ์ง ์๊ณ ์๋ฌ๊ฐ ๋ฐ์ํ ๋ถ๋ถ๋ง ๊ฒฉ๋ฆฌ๋์ด ์ฒ๋ฆฌ๋๋ ๊ฒ์ ๋ณผ ์ ์์ด์.
2๏ธโฃ Suspense์ ErrorBoundary ํจ๊ป ์ฌ์ฉํ๊ธฐ
์ด์ ์ด ๋ ๊ธฐ๋ฅ์ ์กฐํฉํ์ฌ ๋์ฑ ๊ฒฌ๊ณ ํ UI๋ฅผ ๋ง๋ค์ด๋ณผ๊น์? ์๋ฅผ ๋ค์ด, ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ค๋ ๊ณผ์ ์์ ์๋ฌ๊ฐ ๋ฐ์ํ ์๋ ์๊ณ , ๋ฐ์ดํฐ ๋ก๋ฉ ์ค์๋ ๋ก๋ฉ ์คํผ๋๋ฅผ ๋ณด์ฌ์ค์ผ ํ๋ ์ํฉ์ด ํํด์.
// App.tsx (ํตํฉ ์์) import React, { Suspense } from "react"; import ErrorBoundary from "./components/ErrorBoundary"; import UserProfile from "./components/UserProfile"; import LoadingSpinner from "./components/LoadingSpinner"; import BuggyComponent from "./components/BuggyComponent"; import ErrorFallback from "./components/ErrorFallback"; // userResource๋ ์์ ์ ์ํ createSuspenseResource๋ฅผ ์ฌ์ฉํ๋ค๊ณ ๊ฐ์ ํด์. // import { userResource } from "./utils/createSuspenseResource"; // ๊ฐ์์ ์๋ฌ ๋ฐ์ ๋ฐ์ดํฐ ํ์นญ ํจ์ (UserProfile์์ ์ฌ์ฉ๋ ์ ์์) const fetchUserDataWithError = () => new Promise<{ id: number; name: string; email: string; }>((resolve, reject) => { setTimeout(() => { const shouldError = Math.random() > 0.5; // 50% ํ๋ฅ ๋ก ์๋ฌ ๋ฐ์ if (shouldError) { reject(new Error("๋คํธ์ํฌ ์์ฒญ ์ค ์๋ฌ๊ฐ ๋ฐ์ํ์ด์!")); } else { resolve({ id: 2, name: "๋ธ๋ฃจ2", email: "blue2@example.com", }); } }, 2500); }); // userResource๋ฅผ ์๋ฌ ๋ฐ์ ๊ฐ๋ฅ์ฑ์ด ์๋ ๋ฒ์ ์ผ๋ก ๊ต์ฒด (์ค์ ์ฝ๋์์๋ ๋ถ๋ฆฌ ๊ด๋ฆฌ) // export const userResource = createSuspenseResource(fetchUserDataWithError()); function App() { return ( <div className="app-container"> <h1>โจ Suspense & ErrorBoundary ํตํฉ ์์ โจ</h1> <section className="section"> <h2>๐ ๋ฐ์ดํฐ ๋ก๋ฉ๊ณผ ์๋ฌ ์ฒ๋ฆฌ</h2> <ErrorBoundary fallback={<ErrorFallback error={new Error("๋ฐ์ดํฐ ๋ก๋ฉ ์คํจ!")} />}> <Suspense fallback={<LoadingSpinner />}> <UserProfile /> </Suspense> </ErrorBoundary> </section> <section className="section"> <h2>๐ ์ปดํฌ๋ํธ ์๋ฌ ๊ฒฉ๋ฆฌ</h2> <ErrorBoundary fallback={<ErrorFallback error={new Error("์ปดํฌ๋ํธ ๋ ๋๋ง ์คํจ!")} />}> <BuggyComponent /> </ErrorBoundary> </section> <section className="section"> <h2>โ ์ ์์ ์ธ ๋ค๋ฅธ ์ปดํฌ๋ํธ</h2> <div> <p>์ด ๋ถ๋ถ์ ์๋ฌ๋ ๋ก๋ฉ์ ์ํฅ์ ๋ฐ์ง ์๊ณ ์ ์์ ์ผ๋ก ๋ ๋๋ง๋ผ์.</p> <button onClick={() => console.log("์ ์ ๋์!")}>ํด๋ฆญ</button> </div> </section> </div> ); } export default App;
์ด ์์์์๋ UserProfile ์ปดํฌ๋ํธ๋ฅผ Suspense๋ก ๊ฐ์ธ ๋ก๋ฉ ์ํ๋ฅผ ์ฒ๋ฆฌํ๊ณ , ๋ค์ ์ด Suspense์ BuggyComponent๋ฅผ ๊ฐ๊ฐ ErrorBoundary๋ก ๊ฐ์ธ์ ์๋ฌ๋ฅผ ๊ฒฉ๋ฆฌํ์ด์. ์ด๋ ๊ฒ ํ๋ฉด:
UserProfile์ด ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ค๋ ๋์์๋LoadingSpinner๊ฐ ๋ณด์ด๊ณ ,- ๋ฐ์ดํฐ ํ์นญ ์ค ์๋ฌ๊ฐ ๋ฐ์ํ๋ฉด
UserProfile์ ๊ฐ์ผErrorBoundary์ErrorFallback์ด ๋ํ๋๊ณ , BuggyComponent์์ ๋ ๋๋ง ์๋ฌ๊ฐ ๋ฐ์ํ๋ฉด ํด๋น ์ปดํฌ๋ํธ๋ฅผ ๊ฐ์ผErrorBoundary์ErrorFallback์ด ๋ํ๋์.
๊ฐ๊ฐ์ ์๋ฌ์ ๋ก๋ฉ ์ํ๊ฐ ์๋ก์๊ฒ ์ํฅ์ ์ฃผ์ง ์๊ณ ๋ ๋ฆฝ์ ์ผ๋ก ์ฒ๋ฆฌ๋๋ฉด์, ์ฌ์ฉ์์๊ฒ๋ ํจ์ฌ ๋ ์์ ์ ์ด๊ณ ๋ถ๋ถ์ ์ธ ํผ๋๋ฐฑ์ ์ ๊ณตํ๋ UI๋ฅผ ๊ฒฝํํ๊ฒ ๋๋ต๋๋ค.
๐ ์ ๋ฆฌ: ๊ฒฌ๊ณ ํ UI๋ฅผ ํฅํ ์ฒซ๊ฑธ์
React Suspense์ ErrorBoundary๋ ํ๋์ ์ธ React ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ฌ์ฉ์ ๊ฒฝํ์ ํฌ๊ฒ ํฅ์์ํฌ ์ ์๋ ํ์์ ์ธ ๋๊ตฌ์์. ์ด ๋์ ์ ์ ํ ํ์ฉํ๋ฉด '์คํผ๋ ์ง์ฅ'๊ณผ 'ํ๋ฉด ์ ์ฒด ํฌ๋์'์ ๊ฐ์ ๊ณ ์ง์ ์ธ ๋ฌธ์ ๋ค์ ์ฐ์ํ๊ฒ ํด๊ฒฐํ ์ ์์ด์.
0๏ธโฃ ํต์ฌ ์์ฝ
- Suspense: ๋น๋๊ธฐ ๋ฐ์ดํฐ ๋ก๋ฉ ์ค ๋ ๋๋ง์ ์ผ์ ์ค๋จํ๊ณ
fallbackUI๋ฅผ ๋ณด์ฌ์ค์ผ๋ก์จ, ์ ์ธ์ ์ด๊ณ ์ค์ ์ง์คํ๋ ๋ก๋ฉ ์ํ ๊ด๋ฆฌ๋ฅผ ๊ฐ๋ฅํ๊ฒ ํด์. - ErrorBoundary: React ์ปดํฌ๋ํธ ํธ๋ฆฌ ๋ด์์ ๋ฐ์ํ๋ ๋ ๋๋ง ์๋ฌ๋ฅผ ์ก์๋ด๊ณ
fallbackUI๋ฅผ ๋ ๋๋งํ์ฌ, ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฒด ํฌ๋์๋ฅผ ๋ฐฉ์งํ๊ณ ์๋ฌ๋ฅผ ๊ฒฉ๋ฆฌํ๋ฉฐ ์ผ๊ด๋ ์๋ฌ UI๋ฅผ ์ ๊ณตํด์. - ์๋์ง ํจ๊ณผ: Suspense์ ErrorBoundary๋ฅผ ํจ๊ป ์ฌ์ฉํ๋ฉด ๋ฐ์ดํฐ ๋ก๋ฉ ์ค ๋ฐ์ํ ์ ์๋ ์๋ฌ๊น์ง ํจ๊ณผ์ ์ผ๋ก ์ฒ๋ฆฌํ์ฌ, ์ฌ์ฉ์์๊ฒ ๋์ฑ ๊ฒฌ๊ณ ํ๊ณ ๋ถ๋๋ฌ์ด UI ๊ฒฝํ์ ์ ๊ณตํ ์ ์์ด์.
1๏ธโฃ ๋ค์ ์ก์
๊ธฐ์กด ํ๋ก์ ํธ์ Suspense์ ErrorBoundary๋ฅผ ํ ๋ฒ์ ์ ์ฉํ๊ธฐ ์ด๋ ต๋ค๋ฉด, ์์ ๋จ์๋ถํฐ ์ ์ง์ ์ผ๋ก ๋์
ํด ๋ณด์ธ์. ์๋ฅผ ๋ค์ด, ํน์ ๋์๋ณด๋ ์์ ฏ์ด๋ ์ฌ์ฉ์ ํ๋กํ ์ปดํฌ๋ํธ๋ถํฐ ์ ์ฉํด ๋ณด๋ฉด์ ๊ทธ ํจ๊ณผ๋ฅผ ์ง์ ๊ฒฝํํด ๋ณด๋ ๊ฒ์ด ์ข์์.
๋ํ, react-query, swr ๊ฐ์ ๋ฐ์ดํฐ ํ์นญ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ Suspense๋ฅผ ์๋ฒฝํ๊ฒ ์ง์ํ๊ณ ErrorBoundary์๋ ์ ์ฐ๋๋๋, ์ด๋ค์ ํจ๊ป ์ฌ์ฉํ๋ฉด ํจ์ฌ ๋ ํจ์จ์ ์ผ๋ก ๊ฒฌ๊ณ ํ UI๋ฅผ ๊ตฌ์ถํ ์ ์๋ต๋๋ค.
์ฌ์ฉ์์๊ฒ ์ต๊ณ ์ ๊ฒฝํ์ ์ ๊ณตํ๊ธฐ ์ํ ์ฌ์ ์ Suspense์ ErrorBoundary๊ฐ ๋ ๋ ํ ๋๋ฐ์๊ฐ ๋์ด์ค ๊ฑฐ์์. Happy Coding! ๐
๐ฎ ์ฐธ๊ณ
- React ๊ณต์ ๋ฌธ์: Suspense
- React ๊ณต์ ๋ฌธ์: Error Boundaries
- React Query Docs: Suspense
- SWR Docs: Suspense
์ฐ๊ด๋ ํฌ์คํธ
- ๋จ์ด: 1,297๊ฐ16๋ถ
[๐ค] CSS Container Queries: ์ปดํฌ๋ํธ ๊ธฐ๋ฐ ๋ฐ์ํ ๋์์ธ์ ์๋ก์ด ์งํ
๋ฏธ๋์ด ์ฟผ๋ฆฌ์ ํ๊ณ๋ฅผ ๋์ด, ์ปดํฌ๋ํธ ์์ฒด์ ํฌ๊ธฐ์ ๋ฐ๋ผ ์คํ์ผ์ ์กฐ์ ํ๋ CSS Container Queries๋ฅผ ๊น์ด ์๊ฒ ์์๋ณด๊ณ ์ค๋ฌด ์ ์ฉ ๋ฐฉ๋ฒ์ ์๋ดํด ๋๋ ค์.
- ๋จ์ด: 1,673๊ฐ19๋ถ
[๐ค] Next.js 15 ๊ณ ๊ธ ๋ฐ์ดํฐ ์บ์ฑ ์ ๋ต: fetch์ revalidate ์ฌ์ธต ๋ถ์
Next.js 15์์ `fetch` API์ ๊ฐ๋ ฅํ ์บ์ฑ ๋ฉ์ปค๋์ฆ๊ณผ `revalidate` ์ต์ ์ ํ์ฉํ์ฌ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฑ๋ฅ์ ์ต์ ํํ๊ณ ๋ฐ์ดํฐ๋ฅผ ํจ์จ์ ์ผ๋ก ๊ด๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ์ฌ์ธต์ ์ผ๋ก ๋ค๋ฃจ์ด์. ์ค๋ฌด ์์๋ฅผ ํตํด ์๋ฒ ์ปดํฌ๋ํธ์ ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ์์ ์บ์ฑ ์ ๋ต์ ํจ๊ณผ์ ์ผ๋ก ์ ์ฉํ๋ ํ์ ์ ๊ณตํด์.
๋จ์ด: 1,301๊ฐ14๋ถ[๐ค] Next.js App Router: generateStaticParams๋ก ๋์ ๋ผ์ฐํ ๋น๋ ์ต์ ํํ๊ธฐ
Next.js App Router์์ generateStaticParams ํจ์๋ฅผ ํ์ฉํ์ฌ ๋์ ๋ผ์ฐํ ์ ์ ์ ํ์ด์ง๋ฅผ ํจ์จ์ ์ผ๋ก ์์ฑํ๊ณ ๋น๋ ์ฑ๋ฅ์ ์ต์ ํํ๋ ๋ฐฉ๋ฒ์ ์ค์ฉ์ ์ธ ์์์ ํจ๊ป ์์ธํ ์์๋ด์.
๋จ์ด: 1,863๊ฐ22๋ถ[๐ค] React ๋ ๋๋ง ์ต์ ํ: useMemo, useCallback, React.memo ์๋ฒฝ ๊ฐ์ด๋
์ด์ค๊ธ ๊ฐ๋ฐ์๋ฅผ ์ํ React ๋ ๋๋ง ์ต์ ํ ๊ฐ์ด๋. useMemo, useCallback, React.memo์ ์ ํํ ์ฌ์ฉ๋ฒ๊ณผ ์ค๋ฌด์์ ํํ ์ ์ง๋ฅด๋ ์ค์, ๊ทธ๋ฆฌ๊ณ ์ค์ ์ฑ๋ฅ ํฅ์ ์ ๋ต์ ๋ธ๋ฃจ๊ฐ ์๋ ค๋๋ ค์.
๋จ์ด: 2,122๊ฐ24๋ถ[๐ค] JavaScript Proxy์ Reflect ์ฌ์ธต ๋ถ์: ๋ฉํ ํ๋ก๊ทธ๋๋ฐ์ผ๋ก ์ฝ๋ ๊ฐํํ๊ธฐ
JavaScript Proxy์ Reflect API๋ฅผ ํ์ฉํ ๋ฉํ ํ๋ก๊ทธ๋๋ฐ ๊ธฐ๋ฒ์ ์ฌ์ธต ๋ถ์ํด์. ๊ฐ์ฒด ์ ๊ทผ ์ ์ด, ์ ํจ์ฑ ๊ฒ์ฌ, ๋ก๊น , ๋ฐ์ํ ์์คํ ๊ตฌํ ๋ฑ ์ค์ฉ์ ์ธ ํ์ฉ ์ฌ๋ก๋ฅผ ํตํด ์ฝ๋์ ์ ์ฐ์ฑ๊ณผ ์์ ์ฑ์ ๋์ด๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋ณด์ธ์.
- ๋จ์ด: 2,019๊ฐ24๋ถ
[๐ค] React/Next.js ๋ฒ๋ค ์ต์ ํ: ์ฝ๋ ์คํ๋ฆฌํ ๊ณผ ๋ ์ด์ง ๋ก๋ฉ ์๋ฒฝ ๊ฐ์ด๋
React์ Next.js ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ฒ๋ค ํฌ๊ธฐ๋ฅผ ์ค์ด๊ณ ๋ก๋ฉ ์๋๋ฅผ ๊ฐ์ ํ๋ ์ฝ๋ ์คํ๋ฆฌํ ๊ณผ ๋ ์ด์ง ๋ก๋ฉ ๊ธฐ๋ฒ์ ์ค์ฉ์ ์ธ ์์์ ํจ๊ป ์์ธํ ์์๋ด์. ์นํฉ ์ค์ ๋ถํฐ React.lazy, Next.js dynamic import๊น์ง ๋ค๋ค์.
- ๋จ์ด: 1,769๊ฐ20๋ถ
[๐ค] React์ `useOptimistic` ํ ์ผ๋ก ๋๊ด์ UI ์ ๋ฐ์ดํธ ๊ตฌํํ๊ธฐ: Server Actions์ ํจ๊ป
React 18/19์ `useOptimistic` ํ ์ ํ์ฉํ์ฌ Server Actions์ ์ฐ๋๋๋ ๋๊ด์ UI ์ ๋ฐ์ดํธ๋ฅผ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ์ค์ฉ์ ์ธ ์์์ ํจ๊ป ์์ธํ ์์๋ด์. ์ฌ์ฉ์ ๊ฒฝํ์ ๊ฐ์ ํ๊ณ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ฐ์์ฑ์ ๋์ด๋ ๋ ธํ์ฐ๋ฅผ ๊ณต์ ํด์.
๋จ์ด: 1,557๊ฐ17๋ถ[๐ค] TypeScript const Type Parameters: ๋ฆฌํฐ๋ด ํ์ ์ถ๋ก ๊ฐํ์ ์ค์ฉ์ ์ธ ํ์ฉ๋ฒ
TypeScript 5.0์ ๋์ ๋ const Type Parameters๋ฅผ ํ์ฉํ์ฌ ์ ๋ค๋ฆญ ํจ์์ ๋ฆฌํฐ๋ด ํ์ ์ถ๋ก ์ ์ ๊ตํ๊ฒ ์ ์ดํ๊ณ , ๋์ฑ ๊ฒฌ๊ณ ํ ํ์ ์์คํ ์ ๊ตฌ์ถํ๋ ์ค์ฉ์ ์ธ ๋ฐฉ๋ฒ์ ์์๋ณด์ธ์. as const์์ ์ฐจ์ด์ ๊ณผ ์ค์ ์ฝ๋ ์์๋ฅผ ํตํด ์ด์ค๊ธ ๊ฐ๋ฐ์๋ ์ฝ๊ฒ ์ดํดํ ์ ์๋๋ก ์ค๋ช ํด ๋๋ ค์.
- ๋จ์ด: 2,015๊ฐ22๋ถ
[๐ค] Next.js/React ์ฑ CLS ์ต์ ํ: ์ํํธ ์๋ ์ฌ์ฉ์ ๊ฒฝํ ๋ง๋ค๊ธฐ
Next.js์ React ์ ํ๋ฆฌ์ผ์ด์ ์์ Cumulative Layout Shift(CLS) ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ณ ์ฌ์ฉ์ ๊ฒฝํ์ ๊ฐ์ ํ๋ ์ค์ง์ ์ธ ์ ๋ต๊ณผ ์ฝ๋ ์์๋ฅผ ์์ธํ ์์๋ณด์ธ์. ์น ์ฑ๋ฅ ์ต์ ํ์ ํต์ฌ ์์์ธ CLS๋ฅผ ํจ๊ณผ์ ์ผ๋ก ๊ด๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ์๋ ค๋๋ ค์.
- ๋จ์ด: 1,735๊ฐ21๋ถ
[๐ค] Next.js SSR, SSG, ISR ๋ ๋๋ง ์ ๋ต: App Router์์ ์ต์ ์ ์ ํ์?
Next.js App Router์์ ์๋ฒ ์ฌ์ด๋ ๋ ๋๋ง(SSR), ์ ์ ์ฌ์ดํธ ์์ฑ(SSG), ์ฆ๋ถ ์ ์ ์ฌ์์ฑ(ISR) ๊ฐ ๋ ๋๋ง ์ ๋ต์ ๋์ ์๋ฆฌ, ์ฅ๋จ์ , ์ค์ ํ์ฉ ๋ฐ ์ต์ ํ ๋ฐฉ๋ฒ์ ๋น๊ต ๋ถ์ํด๋๋ ค์.
- ๋จ์ด: 1,460๊ฐ17๋ถ
[๐ค] React Context API์ Zustand: ์ ์ญ ์ํ ๊ด๋ฆฌ, ์ธ์ ๋ฌด์์ ์จ์ผ ํ ๊น์?
React ์ ํ๋ฆฌ์ผ์ด์ ์ ์ ์ญ ์ํ ๊ด๋ฆฌ๋ฅผ ๊ณ ๋ฏผํ๊ณ ๊ณ์ ๊ฐ์? Context API์ ๊ฐ๋ฒผ์ด ์ธ๋ถ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ธ Zustand๋ฅผ ๋น๊ต ๋ถ์ํ๊ณ , ์ค๋ฌด์์ ๊ฐ ๋๊ตฌ๋ฅผ ํจ๊ณผ์ ์ผ๋ก ํ์ฉํ๋ ์ ๋ต์ ์ค์ ์ฝ๋ ์์์ ํจ๊ป ์์ธํ ์๋ ค๋๋ ค์.
- ๋จ์ด: 2,004๊ฐ24๋ถ
[๐ค] Turborepo๋ก Next.js ๋ชจ๋ ธ๋ ํฌ ๊ตฌ์ถ: ํจ์จ์ ์ธ ๊ฐ๋ฐ ๋ฐ ์ต์ ํ ์ ๋ต
Turborepo๋ฅผ ํ์ฉํ์ฌ Next.js ํ๋ก์ ํธ๋ฅผ ๋ชจ๋ ธ๋ ํฌ๋ก ๊ตฌ์ฑํ๊ณ , ๊ณต์ ์ปดํฌ๋ํธ, ์ ํธ๋ฆฌํฐ, CI/CD ์ต์ ํ ๋ฐฉ์์ ์ค๋ฌด ์์์ ํจ๊ป ์์ธํ ์ค๋ช ํด ๋๋ ค์.
- ๋จ์ด: 2,318๊ฐ27๋ถ
[๐ค] React useEffect ํ , ์ด์ ํท๊ฐ๋ฆฌ์ง ๋ง์ธ์! (์์กด์ฑ ๋ฐฐ์ด, ํด๋ฆฐ์ ์๋ฒฝ ๊ฐ์ด๋)
React ๊ฐ๋ฐ์์ ํ์์ ์ธ useEffect ํ ์ ๋์ ์๋ฆฌ๋ถํฐ ์์กด์ฑ ๋ฐฐ์ด, ํด๋ฆฐ์ ํจ์ ํ์ฉ๋ฒ, ๊ทธ๋ฆฌ๊ณ ์ค๋ฌด์์ ์์ฃผ ๊ฒช๋ ์ค์์ ์ต์ ํ ์ ๋ต๊น์ง, ์ด์ค๊ธ ๊ฐ๋ฐ์๋ฅผ ์ํ ์๋ฒฝ ๊ฐ์ด๋๋ฅผ ์ ๊ณตํด์.
- ๋จ์ด: 3,270๊ฐ31๋ถ
[๐ค] Next.js Server Actions ์ค์ : ์๋ฌ ์ฒ๋ฆฌ, ์ ํจ์ฑ ๊ฒ์ฌ, ๋๊ด์ UI ์ ๋ฐ์ดํธ
Next.js Server Actions๋ฅผ ์ค๋ฌด์ ์ ์ฉํ ๋ ๋ง์ฃผํ๋ ์๋ฌ ์ฒ๋ฆฌ, ๋ฐ์ดํฐ ์ ํจ์ฑ ๊ฒ์ฌ, ๊ทธ๋ฆฌ๊ณ ์ฌ์ฉ์ ๊ฒฝํ์ ํฅ์์ํค๋ ๋๊ด์ UI ์ ๋ฐ์ดํธ ๊ธฐ๋ฒ์ ์์ธํ ์ฝ๋ ์์์ ํจ๊ป ์์๋ณด์ธ์.
๋จ์ด: 1,981๊ฐ21๋ถ[๐ค] TypeScript ์ ํธ๋ฆฌํฐ ํ์ ์๋ฒฝ ๊ฐ์ด๋: ์ค์ ํ์ฉ ํจํด
TypeScript ์ ํธ๋ฆฌํฐ ํ์ ์ ํต์ฌ ๊ฐ๋ ๊ณผ ์ค์ ํ์ฉ๋ฒ์ ๊น์ด ์๊ฒ ๋ค๋ค์. Pick, Omit, Partial, Required ๋ฑ ์์ฃผ ์ฐ๋ ์ ํธ๋ฆฌํฐ ํ์ ์ผ๋ก ๋ณต์กํ ํ์ ์ ํจ๊ณผ์ ์ผ๋ก ๋ค๋ฃจ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋ณด์ธ์. ํ์ ์คํฌ๋ฆฝํธ ์ฝ๋์ ์ฌ์ฌ์ฉ์ฑ๊ณผ ์์ ์ฑ์ ๋์ด๋ ๋ ธํ์ฐ๋ฅผ ๊ณต์ ํด์.
- ๋จ์ด: 1,707๊ฐ20๋ถ
[๐ค] Next.js App Router ๋ฏธ๋ค์จ์ด: ๊ฐ๋ ฅํ ์์ฒญ ์ฒ๋ฆฌ ์ ๋ต๊ณผ ์ค์ ์์
Next.js App Router ํ๊ฒฝ์์ ๋ฏธ๋ค์จ์ด๋ฅผ ํ์ฉํด ์ฌ์ฉ์ ์ธ์ฆ, ๋ฆฌ๋ค์ด๋ ์ , ๊ตญ์ ํ ๋ฑ์ ์์ฒญ ์ฒ๋ฆฌ ๋ก์ง์ ํจ์จ์ ์ผ๋ก ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ์ค์ ์์ ์ ํจ๊ป ์์ธํ ์์๋ณด์ธ์.
- ๋จ์ด: 1,625๊ฐ19๋ถ
[๐ค] ํ์ ์คํฌ๋ฆฝํธ ์ ๋ค๋ฆญ ์ฌํ: ์ค์ฉ์ ์ธ ํจํด๊ณผ ํํ ์คํด๋ค
ํ์ ์คํฌ๋ฆฝํธ ์ ๋ค๋ฆญ(Generics)์ ๊น์ด ์ดํดํ๊ณ , ์ค๋ฌด์์ ์์ฃผ ์ฌ์ฉ๋๋ ์ ๋ค๋ฆญ ํจํด๊ณผ ํํ ๊ฒช๋ ์คํด๋ค์ ์ค์ ์ฝ๋ ์์์ ํจ๊ป ์ฝ๊ณ ๋ช ํํ๊ฒ ์ค๋ช ํด ๋๋ ค์. ํ์ ์์ ์ฑ๊ณผ ์ฝ๋ ์ฌ์ฌ์ฉ์ฑ์ ๋์ด๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋ณด์ธ์.
๋จ์ด: 1,846๊ฐ18๋ถ[๐ค] Next.js Route Handler: App Router์์ ์์ ํ๊ณ ํจ์จ์ ์ธ API ๊ตฌ์ถํ๊ธฐ (์ธ์ฆ, ์๋ฌ ์ฒ๋ฆฌ ํฌํจ)
Next.js App Router์ Route Handler๋ฅผ ์ฌ์ฉํ์ฌ API ์๋ํฌ์ธํธ๋ฅผ ๊ตฌ์ถํ๋ ๋ฐฉ๋ฒ์ ์์ธํ ์์๋ด์. ์ธ์ฆ, ์๋ฌ ์ฒ๋ฆฌ, ๊ทธ๋ฆฌ๊ณ ์บ์ฑ ์ ๋ต์ ํฌํจํ ์ค์ฉ์ ์ธ ํ์ผ๋ก ์์ ํ๊ณ ํจ์จ์ ์ธ ์๋ฒ๋ฆฌ์ค ํจ์๋ฅผ ๋ง๋๋ ๋ฐฉ๋ฒ์ ์ตํ๋ด์.
- ๋จ์ด: 1,932๊ฐ22๋ถ
[๐ค] Next.js Image ์ปดํฌ๋ํธ ์ต์ ํ: Core Web Vitals ๊ฐ์ ๋ถํฐ ์ค์ ํ์ฉ๊น์ง
Next.js์ Image ์ปดํฌ๋ํธ๋ฅผ ํ์ฉํ์ฌ ์น ์ฑ๋ฅ ํต์ฌ ์งํ์ธ Core Web Vitals๋ฅผ ๊ฐ์ ํ๊ณ , ๋ค์ํ ์ต์ ํ ์ต์ ์ ์ค์ ํ๋ก์ ํธ์ ์ ์ฉํ๋ ๋ฐฉ๋ฒ์ ๋ธ๋ฃจ๊ฐ ์์ธํ ์๋ ค๋๋ ค์.
- ๋จ์ด: 2,173๊ฐ25๋ถ
[๐ค] Next.js 14.1+์ ํ์ : Partial Prerendering (PPR) ์๋ฒฝ ๊ฐ์ด๋์ ์ค์ ์ต์ ํ ์ ๋ต
Next.js 14.1๋ถํฐ ๋์ ๋ Partial Prerendering (PPR)์ ํตํด ์ด๊ธฐ ๋ก๋ฉ ์๋๋ฅผ ๊ทน๋ํํ๊ณ ๋์ ์ฝํ ์ธ ๋ฅผ ํจ์จ์ ์ผ๋ก ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ์ฌ๋ ์๊ฒ ๋ค๋ฃจ์ด์. PPR์ ๋์ ์๋ฆฌ๋ถํฐ ์ค์ ํ๋ก์ ํธ ์ ์ฉ ์ ๋ต๊น์ง, ๊ฐ๋ฐ์๋ค์ด ๊ถ๊ธํดํ๋ ๋ชจ๋ ๊ฒ์ ์๋ ค๋๋ ค์.
- ๋จ์ด: 1,786๊ฐ19๋ถ
[๐ค] TypeScript ์กฐ๊ฑด๋ถ ํ์ ๊ณผ infer ํค์๋: ๋ณต์กํ ํ์ ๋ ์์ฝ๊ฒ ๋ค๋ฃจ๋ ๋ฐฉ๋ฒ
TypeScript ๊ฐ๋ฐ์์ ๋ง์ฃผํ๋ ๋ณต์กํ ํ์ ์ถ๋ก ๋ฌธ์ , ์กฐ๊ฑด๋ถ ํ์ ๊ณผ infer ํค์๋๋ฅผ ํ์ฉํ๋ฉด ํจ์ฌ ์ฐ์ํ๊ณ ๊ฐ๋ ฅํ๊ฒ ํด๊ฒฐํ ์ ์์ด์. ์ค์ ์์ ์ ํจ๊ป ๊ทธ ํ์ฉ๋ฒ์ ์ฌ๋ ์๊ฒ ๋ค๋ค๋ด ๋๋ค.
- ๋จ์ด: 1,697๊ฐ21๋ถ
[๐ค] JavaScript ์ด๋ฒคํธ ๋ฃจํ(Event Loop) ์์ ์ ๋ณต: ๋น๋๊ธฐ ์ฒ๋ฆฌ์ ๋ฐํ์ ๋์ ์๋ฆฌ
JavaScript์ ํต์ฌ ๋น๋๊ธฐ ์ฒ๋ฆฌ ๋ฉ์ปค๋์ฆ์ธ ์ด๋ฒคํธ ๋ฃจํ์ ๋์ ์๋ฆฌ๋ฅผ ์ฌ๋ ์๊ฒ ํํค์ณ ๋ด์. ์ฝ ์คํ, ํ์คํฌ ํ, ๋ง์ดํฌ๋กํ์คํฌ ํ์์ ์ํธ์์ฉ์ ์ดํดํ๊ณ , ์ค๋ฌด์์ ๋ง์ฃผ์น๋ ๋น๋๊ธฐ ์ฝ๋์ ๋์์ ๋ช ํํ ์์ธกํ๋ ๋ฐฉ๋ฒ์ ์๋ ค๋๋ ค์.
- ๋จ์ด: 1,960๊ฐ23๋ถ
[๐ค] Next.js Server & Client Components, ์ค์ ์์ ํ๋ช ํ๊ฒ ์ ํํ๋ ๊ฐ์ด๋
Next.js App Router์์ Server Components์ Client Components ์ค ์ด๋ค ๊ฒ์ ์ฌ์ฉํด์ผ ํ ์ง ๊ณ ๋ฏผ์ด์ ๊ฐ์? ์ด ๊ธ์์ ๋ ์ปดํฌ๋ํธ์ ํต์ฌ ์ฐจ์ด์ , ์ฌ์ฉ ์์ , ๊ทธ๋ฆฌ๊ณ ์ฑ๋ฅ ์ต์ ํ๋ฅผ ์ํ ์ค์ ์ ๋ต์ ๋ธ๋ฃจ๊ฐ ์๋ ค๋๋ฆด๊ฒ์.
- ๋จ์ด: 1,878๊ฐ21๋ถ
[๐ค] TypeScript satisfies ์ฐ์ฐ์: ํ์ ์ถ๋ก ๊ณผ ์์ ์ฑ์ ๋์์ ์ก๋ ๋น๋ฒ
TypeScript์ `satisfies` ์ฐ์ฐ์๋ฅผ ํ์ฉํ์ฌ ํ์ ์ถ๋ก ์ ์ ์ฐ์ฑ์ ์ ์งํ๋ฉด์๋ ์๊ฒฉํ ํ์ ์์ ์ฑ์ ํ๋ณดํ๋ ๋ฐฉ๋ฒ์ ์์๋ณด์ธ์. ์ค์ฉ์ ์ธ ์์๋ฅผ ํตํด ์ค์ ํ๋ก์ ํธ์ ์ ์ฉํ๋ ๋ ธํ์ฐ๋ฅผ ๊ณต์ ํฉ๋๋ค.
- ๋จ์ด: 1,207๊ฐ15๋ถ
[๐ค] React 19 ์๋ก์ด ๊ธฐ๋ฅ: use ํ , Actions, ๊ทธ๋ฆฌ๊ณ ์ปดํ์ผ๋ฌ ๋ฏธ๋ฆฌ๋ณด๊ธฐ
React 19์ ํต์ฌ ๋ณ๊ฒฝ ์ฌํญ์ธ use ํ , ์๋ฒ ์ก์ , ๊ทธ๋ฆฌ๊ณ React ์ปดํ์ผ๋ฌ์ ๋์ ๋ฐฐ๊ฒฝ๊ณผ ์ค์ ํ์ฉ ์์๋ฅผ ์ด์ค๊ธ ๊ฐ๋ฐ์ ๋๋์ด์ ๋ง์ถฐ ์์ธํ ์ค๋ช ํฉ๋๋ค. ์ต์ React ์ ๋ฐ์ดํธ๋ฅผ ํตํด ์ ํ๋ฆฌ์ผ์ด์ ์ฑ๋ฅ๊ณผ ๊ฐ๋ฐ ๊ฒฝํ์ ํฅ์์ํค๋ ๋ฐฉ๋ฒ์ ์์๋ณด์ธ์.
- ๋จ์ด: 1,512๊ฐ16๋ถ
[๐ค] Next.js App Router ์บ์ฑ ์ ๋ต: ๋ฐ์ดํฐ ์ฌ๊ฒ์ฆ (revalidatePath, revalidateTag) ์๋ฒฝ ๊ฐ์ด๋
Next.js 14 App Router์์ ํจ์จ์ ์ธ ๋ฐ์ดํฐ ์บ์ฑ ์ ๋ต๊ณผ revalidatePath, revalidateTag๋ฅผ ์ด์ฉํ ๋ฐ์ดํฐ ์ฌ๊ฒ์ฆ ๋ฐฉ๋ฒ์ ์ค๋ฌด ์์์ ํจ๊ป ์์ธํ ์์๋ณด๊ณ ์น ์ฑ๋ฅ์ ์ต์ ํํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋ณด์ธ์.