๐Ÿ’ฅ 16. ๋น„๋™๊ธฐ UX์˜ ํŒจ๋Ÿฌ๋‹ค์ž„ ์ „ํ™˜ (Suspense & ErrorBoundary)

2026๋…„ 4์›” 30์ผ ์ˆ˜์ •๋จ

๐Ÿ“‹ ๊ฐœ์š”

๋กœ๋”ฉ๊ณผ ์—๋Ÿฌ ์ฒ˜๋ฆฌ ์ฑ…์ž„์„ ๋ถ€๋ชจ ๋ ˆ์ด์–ด๋กœ ์™„๋ฒฝํžˆ ๋– ๋„˜๊ธฐ๊ณ , ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋Š” '๋ฐ์ดํ„ฐ๊ฐ€ 100% ์กด์žฌํ•  ๋•Œ์˜ ์„ฑ๊ณตํ•œ UI'๋งŒ ๋‚จ๊ฒจ๋‘๋Š” ๊ทน๊ฐ•์˜ ์„ ์–ธ์  ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ ๊ธฐ๋ฒ•์„ ๋ฐฐ์›๋‹ˆ๋‹ค.

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • if (isLoading)๊ณผ if (isError)๋กœ ๋„๋ฐฐ๋œ ๋ฌธ์ œ๊ฐ€ ํฐ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ตฌ์ถœํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์ปดํฌ๋„ŒํŠธ์˜ ์ฑ…์ž„์„ "๋ฐ์ดํ„ฐ ๋ Œ๋”๋ง" ํ•˜๋‚˜๋กœ๋งŒ ๊ทน๋‹จ์ ์œผ๋กœ ์ขํžˆ๋Š” ์„ ์–ธ์ (Declarative) ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ์˜ ๋งˆ๋ฒ•์„ ์ดํ•ดํ•œ๋‹ค.
  • ์•ฑ ์–ด๋””์„œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋“  ์•ฑ ์ „์ฒด๊ฐ€ ํ•˜์–—๊ฒŒ ์‚ฌ๋ผ์ง€์ง€ ์•Š๊ณ (ํ™”์ดํŠธ์•„์›ƒ), ์šฐ์•„ํ•˜๊ฒŒ ๋ถ€๋ถ„ ์—๋Ÿฌ ํ™”๋ฉด์„ ๋„์›Œ์ฃผ๋Š” ์•„ํ‚คํ…์ฒ˜๋ฅผ ์„ค๊ณ„ํ•  ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ“‹ ๋ชฉ์ฐจ


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

โฑ๏ธ ์˜ˆ์ƒ ์ฝ๊ธฐ ์‹œ๊ฐ„: 12๋ถ„ / ํ•ต์‹ฌ ํŒŒํŠธ: 7๋ถ„

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

  • ์˜์ˆ˜(PM):"์˜์ฒ  ๋‹˜, ์œ ์ € ํ”„๋กœํ•„ ํŽ˜์ด์ง€ ๋“ค์–ด๊ฐ”์„ ๋•Œ ๋กœ๋”ฉ ๋น™๊ธ€๋น™๊ธ€ ๋„๋Š” ์Šคํ”ผ๋„ˆ ๋ง์ด์—์š”. ์ง€๊ธˆ์€ ์ „์ฒด ํ™”๋ฉด์„ ๊ฐ€๋ฆฌ๋Š”๋ฐ, ํ”„๋กœํ•„ ์‚ฌ์ง„ ์ชฝ๋งŒ ๋”ฐ๋กœ ๋Œ๊ณ , ์ž‘์„ฑํ•œ ๊ธ€ ๋ชฉ๋ก ์ชฝ ๋”ฐ๋กœ ๋Œ๊ฒŒ ์Šค๋ฌด์Šคํ•˜๊ฒŒ ๋งŒ๋“ค์–ด์ฃผ์‹ค ์ˆ˜ ์žˆ๋‚˜์š”? ์•„์ฐธ, ๊ธ€ ๋ชฉ๋ก ๋ถˆ๋Ÿฌ์˜ค๋‹ค ์—๋Ÿฌ ๋‚˜๋ฉด ๊ฑฐ๊ธด '๋‹ค์‹œ ์‹œ๋„' ๋ฒ„ํŠผ ๋‹ฌ์•„์ฃผ์‹œ๊ณ ์š”!"
  • ์˜์ฒ (์‹ ์ž…):"๋„ค? ๊ทธ๋Ÿผ ProfileAvatar ์ปดํฌ๋„ŒํŠธ ์•ˆ์—๋„ isLoading, isError ์ƒํƒœ ๋งŒ๋“ค๊ณ ... PostList ์ปดํฌ๋„ŒํŠธ ์•ˆ์—๋„ isLoading, isError ์ƒํƒœ ๋งŒ๋“ค์–ด์„œ ๋‹ค if (isLoading) return <Spinner/>๋ฅผ ๋„ฃ์–ด์•ผ๊ฒ ๋„ค์š”... ์ฝ”๋“œ๊ฐ€ 3๋ฐฐ๋กœ ๋ถˆ์–ด๋‚  ํ…๋ฐ ใ… ใ… "
  • ์˜ํ˜ธ(๋ฆฌ๋“œ): "์˜์ฒ  ๋‹˜, ๋กœ๋”ฉ๊ณผ ์—๋Ÿฌ ์กฐ๊ฑด๋ฌธ์ด ์ปดํฌ๋„ŒํŠธ ์•ˆ์— ๋งŽ์ด ํฉ์–ด์ ธ ์žˆ๊ตฐ์š”. ์ปดํฌ๋„ŒํŠธ๋Š” '๋ฐ์ดํ„ฐ๊ฐ€ ์˜ค๋ฉด ์–ด๋–ป๊ฒŒ ์˜ˆ์˜๊ฒŒ ๊ทธ๋ฆด๊นŒ?' ๋‹จ ํ•˜๋‚˜์—๋งŒ ์ง‘์ค‘ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์—๋Ÿฌ์™€ ๋กœ๋”ฉ ์ฒ˜๋ฆฌ๋Š” ์ƒ์œ„ ๊ฒฝ๊ณ„๋กœ ์˜ฌ๋ฆฌ์„ธ์š”. Suspense ์™€ ErrorBoundary ๋กœ ์ •๋ฆฌํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค."

๐Ÿค” ์™œ ์•Œ์•„์•ผ ํ•˜๋Š”๊ฐ€: ๋ช…๋ นํ˜• ๋กœ๋”ฉ/์—๋Ÿฌ ์ฒ˜๋ฆฌ์˜ ์žฅ์• 

1~3๋…„ ์ฐจ ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž๋“ค์˜ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด ์‹ญ์ค‘ํŒ”๊ตฌ ์•„๋ž˜์™€ ๊ฐ™์€ ํ˜•ํƒœ๋ฅผ ๋•๋‹ˆ๋‹ค.

// โŒ ์˜์ฒ ์ด์˜ ํ”ํ•œ ๋ช…๋ นํ˜• ๋น„๋™๊ธฐ ์ปดํฌ๋„ŒํŠธ (๊ด€์‹ฌ์‚ฌ ํ˜ผ์žฌ)
function UserProfile({ userId }) {
  const [data, setData] = useState(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);
 
  useEffect(() => {
    fetchUserData(userId)
      .then(res => setData(res))
      .catch(err => setError(err))
      .finally(() => setIsLoading(false));
  }, [userId]);
 
  // ๐Ÿšจ UI๋ฅผ ๊ทธ๋ฆฌ๋Š” ๋ณธ์—ฐ์˜ ์ž„๋ฌด ์ „์— ๋ฐฉ์–ด๋ง‰(์กฐ๊ฑด๋ฌธ)์ด ๋„ˆ๋ฌด๋‚˜๋„ ๋งŽ๋‹ค!
  if (isLoading) return <Spinner />;
  if (error) return <ErrorMessage message={error.message} />;
 
  // ์—ฌ๊ธฐ๊นŒ์ง€ ์‚ด์•„๋‚จ์•„์•ผ ๊ฒจ์šฐ ์ง„์งœ ๋ชฉ์ (UI ๋ Œ๋”๋ง) ๋‹ฌ์„ฑ ๊ฐ€๋Šฅ
  return <div>ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค, {data.name} ๋‹˜!</div>;
}

์ด ๋ฐฉ์‹์˜ ์น˜๋ช…์ ์ธ ๋ฌธ์ œ์ ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  1. ๊ด€์‹ฌ์‚ฌ ์˜ค์—ผ: ์ปดํฌ๋„ŒํŠธ์˜ ๋ณธ์งˆ์€ "๋ฐ์ดํ„ฐ๋ฅผ ์˜ˆ์˜๊ฒŒ UI๋กœ ๋งตํ•‘ํ•˜๋Š” ๊ฒƒ"์ธ๋ฐ, ๋ฐ์ดํ„ฐ๋ฅผ ์–ด๋–ป๊ฒŒ ๊ฐ€์ ธ์˜ฌ์ง€, ์‹คํŒจํ•˜๋ฉด ์–ด๋–กํ• ์ง€ ์˜จ๊ฐ– ์žก์ผ์— ์‹œ๋‹ฌ๋ฆฝ๋‹ˆ๋‹ค.
  2. **"์ด ๋ฐ์ดํ„ฐ๋Š” ์ง„์งœ ์žˆ๋Š”๊ฐ€?":**TypeScript๋กœ ๋‹ฌ์•„๋ด๋„ data๋Š” ์ดˆ๊ธฐ์— null์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋ฐ‘์—์„œ data.name์„ ์“ธ ๋•Œ๋งˆ๋‹ค ๋ถˆ์•ˆ์ฆ์„ธ(Optional Chaining data?.name)์— ์‹œ๋‹ฌ๋ฆฌ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
  3. UI ๊ธฐํš ๋ณ€๊ฒฝ ๋Œ€์‘ ๋ถˆ๊ฐ€: PM์ด "๋กœ๋”ฉ ๋ฐ”๋ฅผ ์ € ์œ„์ชฝ ํ—ค๋”๋กœ ํ•ฉ์ณ์ฃผ์„ธ์š”"๋ผ๊ณ  ํ•˜๋ฉด, ๋กœ๋”ฉ ์ƒํƒœ๋ฅผ ๋˜ ๋ถ€๋ชจ๋กœ ๋Œ์–ด์˜ฌ๋ฆฌ๋ฉฐ(Lifting State Up) ๋Œ€๊ณต์‚ฌ๋ฅผ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ—๏ธ ๋น„์œ ๋กœ ๋จผ์ € ์ดํ•ดํ•˜๊ธฐ

๐Ÿง’ 5์‚ด์—๊ฒŒ ์„ค๋ช…ํ•œ๋‹ค๋ฉด?

  1. **๊ธฐ์กด ๋ฐฉ์‹ (์˜์ฒ ์ด ํ–„๋ฒ„๊ฑฐ ๊ฐ€๊ฒŒ):**์š”๋ฆฌ์‚ฌ(์ปดํฌ๋„ŒํŠธ)๊ฐ€ ํŒจํ‹ฐ๋ฅผ ๊ตฝ๋‹ค๊ฐ€ ๊ณ ๊ธฐ๊ฐ€ ๋–จ์–ด์ง€๋ฉด(๋กœ๋”ฉ), ์ž๊ธฐ๊ฐ€ ์ง์ ‘ ์š”๋ฆฌ๋ฅผ ๋ฉˆ์ถ”๊ณ  ํ™€์— ๋‚˜๊ฐ€ "์†๋‹˜, ๊ณ ๊ธฐ ์˜ค๋Š” ์ค‘ ใ… ใ… " ํŒป๋ง์„ ๋“ญ๋‹ˆ๋‹ค. ๊ณ ๊ธฐ๊ฐ€ ์ƒํ–ˆ์œผ๋ฉด(์—๋Ÿฌ) "์†๋‹˜ ๊ณ ๊ธฐ ์ƒํ•จ!" ํŒป๋ง์„ ๋“ค๊ณ ์š”. ์š”๋ฆฌ์— ์ง‘์ค‘ํ•  ์ˆ˜๊ฐ€ ์—†์ฃ .
  2. Suspense & ErrorBoundary ๋ฐฉ์‹ (์˜ํ˜ธ์˜ ๊ณ ๊ธ‰ ๋ ˆ์Šคํ† ๋ž‘): ์š”๋ฆฌ์‚ฌ๋Š” "๋‚œ ๋ฌด์กฐ๊ฑด ๊ณ ๊ธฐ(๋ฐ์ดํ„ฐ)๊ฐ€ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๊ณ  ํ–„๋ฒ„๊ฑฐ ์กฐ๋ฆฝ๋งŒ ํ•œ๋‹ค" ๋ผ๊ณ  ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค.
    ๋งŒ์•ฝ ๊ณ ๊ธฐ๊ฐ€ ์•„์ง ์•ˆ ์™”์œผ๋ฉด? ์š”๋ฆฌ์‚ฌ๋Š” ์ฃผ๋ฐฉ ๋ฐ–์œผ๋กœ ์†Œ๋ฆฌ๋ฅผ ์ง€๋ฅด๋ฉฐ ์“ฐ๋Ÿฌ์ง‘๋‹ˆ๋‹ค(Throw). ๊ทธ๋Ÿผ ๋งค๋‹ˆ์ €(Suspense)๊ฐ€ ํŠ€์–ด๋‚˜์™€์„œ ์†๋‹˜์—๊ฒŒ ์šฐ์•„ํ•˜๊ฒŒ ์‹์ „ ๋นต(Spinner)์„ ๋Œ€์ ‘ํ•˜๋ฉฐ ๊ธฐ๋‹ค๋ฆฌ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.
    ๋งŒ์•ฝ ๊ณ ๊ธฐ๊ฐ€ ์ƒํ–ˆ์œผ๋ฉด? ์—ญ์‹œ ์†Œ๋ฆฌ์ง€๋ฅด๋ฉฐ ์“ฐ๋Ÿฌ์ง€๊ณ , ์ ์žฅ(ErrorBoundary)์ด ๋›ฐ์–ด๋‚˜์™€ ์ •์ค‘ํ•˜๊ฒŒ ์‚ฌ๊ณผ ์ฟ ํฐ(์—๋Ÿฌ UI)์„ ๋“œ๋ฆฝ๋‹ˆ๋‹ค. ์š”๋ฆฌ์‚ฌ๋Š” ์˜ค์ง '์š”๋ฆฌ'๋ผ๋Š” ๋ณธ์งˆ์—๋งŒ ์ง‘์ค‘ํ•ฉ๋‹ˆ๋‹ค!

์ด๊ฒƒ์ด ๋ฆฌ์•กํŠธ๊ฐ€ ๋ฐ€๊ณ  ์žˆ๋Š” "๋Œ€์ˆ˜์  ํšจ๊ณผ(Algebraic Effects)" ์˜ ๋ฉ˜ํƒˆ ๋ชจ๋ธ์ž…๋‹ˆ๋‹ค. ์ž์‹ ์ปดํฌ๋„ŒํŠธ์—์„œ ๊ฐ๋‹นํ•  ์ˆ˜ ์—†๋Š” ์‚ฌ์ด๋“œ ์ดํŽ™ํŠธ(๋กœ๋”ฉ, ์—๋Ÿฌ)๋ฅผ ๋ถ€๋ชจ๊ฐ€ ์บ์น˜ํ•˜์—ฌ ๋Œ€์‹  ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ ์ด์ฃ .


๐Ÿงฉ ๊ทน์˜ ์ฒด๋“: ์„ ์–ธ์  ๋น„๋™๊ธฐ UX ์„ค๊ณ„ํ•˜๊ธฐ

์˜์ฒ ์ด์˜ ๋ฌธ์ œ๊ฐ€ ์ปธ๋˜ ์ฝ”๋“œ๋ฅผ Suspense ์™€ ErrorBoundary (React 18 & Next.js ์‹œ๋Œ€์˜ ํ‘œ์ค€ ๊ต์–‘)๋ฅผ ์จ์„œ ๋ฆฌํŒฉํ† ๋งํ•ด ๋ด…์‹œ๋‹ค. (ํ˜„์—…์—์„œ๋Š” React Query์˜ useSuspenseQuery ๋“ฑ๊ณผ ๊ฒฐํ•ฉํ•ด ๋งŽ์ด ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.)

โœ… 1๋‹จ๊ณ„: ์˜ํ˜ธ์˜ ์„ ์–ธ์  ์ปดํฌ๋„ŒํŠธ ์„ค๊ณ„

์šฐ์„  ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋Š” ์—๋Ÿฌ๊ณ  ๊ฐ™์€ ํ‘œํ˜„์€ ๋‹ค ๋ฒ„๋ฆฌ๊ณ  ํ•ต์‹ฌ ๋ฐ์ดํ„ฐ๋งŒ ๋ณด๊ณ  ๋Œ์ง„ํ•˜๋„๋ก ๋‡Œ๋ฅผ ๋น„์›๋‹ˆ๋‹ค.

// ๐ŸŽฏ 1. ๋‡Œ๋ฅผ ๋น„์šด ์„ ์–ธ์  ์ž์‹ ์ปดํฌ๋„ŒํŠธ (์„ฑ๊ณตํ•œ ๋ฏธ๋ž˜๋งŒ ๋ฐ”๋ผ๋ณธ๋‹ค)
function UserProfile({ userId }) {
  // isLoading? isError? ๊ทธ๋Ÿฐ ๊ฑด ๋ถ€๋ชจ๋‹˜์ด ์•Œ์•„์„œ ํ•˜์‹ญ๋‹ˆ๋‹ค.
  // ๋‚˜๋Š” '๋ฐ์ดํ„ฐ๊ฐ€ ๋ฌด์กฐ๊ฑด ์™„๋ฒฝํ•˜๊ฒŒ ์˜จ ์ƒํƒœ'๋ผ๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค!
  const { data } = useSuspenseQuery(fetchUserData, userId);
 
  // optional chaining(data?.name) ๋”ฐ์œ„๋Š” ๊ฐœ๋‚˜ ์ค๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋Š” ์–ธ์ œ๋‚˜ 100% ์žˆ์Šต๋‹ˆ๋‹ค.
  return (
    <div className="profile-card">
      <img src={data.profileImage} />
      <h3>ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค, {data.name} ๋‹˜!</h3>
    </div>
  );
}

โœ… 2๋‹จ๊ณ„: ๋งค๋‹ˆ์ €(Suspense)์™€ ์ ์žฅ(ErrorBoundary) ๋ฐฐ์น˜

์ž์‹ ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋™์•ˆ ๋˜์ง„(Throw) Promise ํŽœ๋”ฉ ์ƒํƒœ์™€ Error ๋ฆฌ์ ์…˜ ์ƒํƒœ๋ฅผ ๋ถ€๋ชจ ๊ณ„์ธต์—์„œ ์žก์•„์ฑ•๋‹ˆ๋‹ค.

// ๐ŸŽฏ 2. ๋น„๋™๊ธฐ ์ฑ…์ž„์„ ๋ชจ๋‘ ๋– ์•ˆ์€ ๋ถ€๋ชจ ์ปจํ…Œ์ด๋„ˆ
import { Suspense } from 'react';
import { ErrorBoundary } from 'react-error-boundary'; // ์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉ ๊ฐ•๋ ฅ ๊ถŒ์žฅ
 
function UserPage({ userId }) {
  return (
    <div className="page-layout">
      <h1>์œ ์ € ํŽ˜์ด์ง€</h1>
 
      {/* ๐Ÿ”ด ์ ์žฅ: ๋งŒ์•ฝ ์•ˆ์—์„œ ์น˜๋ช…์  ์—๋Ÿฌ๊ฐ€ ๋‚˜๋ฉด ์ด UI๋กœ ๋ฎ์–ด์”Œ์›€! */}
      <ErrorBoundary fallback={<div>ํ”„๋กœํ•„์„ ๋ถˆ๋Ÿฌ์˜ค๋‹ค ์„œ๋ฒ„๊ฐ€ ํ„ฐ์กŒ์Šต๋‹ˆ๋‹ค ๐Ÿ˜ญ</div>}>
 
        {/* ๐ŸŸก ๋งค๋‹ˆ์ €: ์•ˆ์—์„œ ์•„์ง ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋Š” ์ค‘์ด๋ฉด ์ด Spinner๋ฅผ ๊ทธ๋ ค์คŒ! */}
        <Suspense fallback={<Spinner size="large" />}>
 
          {/* ์„ฑ๊ณตํ•œ ๋ฏธ๋ž˜๋งŒ ๊ทธ๋ฆฌ๋Š” ์š”๋ฆฌ์‚ฌ ์ปดํฌ๋„ŒํŠธ */}
          <UserProfile userId={userId} />
 
        </Suspense>
 
      </ErrorBoundary>
    </div>
  );
}

โœ… 3๋‹จ๊ณ„: ๊ธฐํš ๋ณ€๊ฒฝ? ๋ ˆ๊ณ  ์กฐ๋ฆฝํ•˜๋“ฏ ์“ฑ์‹น!

"์˜์ฒ ์”จ, ํ”„๋กœํ•„ ๋ง๊ณ  ํ•˜๋‹จ์— ์žˆ๋Š” '์ตœ๊ทผ ์ž‘์„ฑ ๊ธ€ ๋ชฉ๋ก'๋„ ๋”ฐ๋กœ ๋กœ๋”ฉ ๋Œ๊ฒŒ ํ•ด์ฃผ์„ธ์š”~"
๊ณผ๊ฑฐ์—” ๋‚ด๋ถ€ ๋‚ด๋ถ€ ์ฝ”๋“œ๋ฅผ ํฌ๊ฒŒ ๊ณ ์ณค๊ฒ ์ง€๋งŒ, ์ง€๊ธˆ์€ ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋“ค์€ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š๊ณ  ๋ถ€๋ชจ์˜ ๋ ˆ์ด์•„์›ƒ ๊ป๋ฐ๊ธฐ๋งŒ ํŠœ๋‹ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

// ๐ŸŽฏ 3. ๊ทนํ•œ์˜ ๋ถ„๋ฆฌ ๋•์— ์–ป๊ฒŒ ๋œ ์™„๋ฒฝํ•œ UI ๋ ˆ์ด์•„์›ƒ ํ†ต์ œ๊ถŒ
function UserPage({ userId }) {
  return (
    <>
      <ErrorBoundary fallback={<TextError />}>
        {/* ํ”„๋กœํ•„์€ ํ”„๋กœํ•„๋Œ€๋กœ ๋”ฐ๋กœ ๋น™๊ธ€๋น™๊ธ€ ๋Œ๊ณ ~ */}
        <Suspense fallback={<ProfileSkeleton />}>
          <UserProfile userId={userId} />
        </Suspense>
      </ErrorBoundary>
 
      <ErrorBoundary fallback={<Button>๊ธ€ ๋ชฉ๋ก ๊ฐฑ์‹  ์‹คํŒจ. ์žฌ์‹œ๋„</Button>}>
        {/* ๊ฒŒ์‹œ๋ฌผ ๋ชฉ๋ก์€ ๋˜ ๊ฑ”๋„ค๋Œ€๋กœ ๋”ฐ๋กœ ๋น™๊ธ€๋น™๊ธ€ ๋•๋‹ˆ๋‹ค! (๋ณ‘๋ ฌ ๋กœ๋”ฉ) */}
        <Suspense fallback={<ListSkeleton />}>
          <UserPosts userId={userId} />
        </Suspense>
      </ErrorBoundary>
    </>
  );
}

์ด ๊ตฌ์กฐ ๋•๋ถ„์— ํŠน์ • ๊ตฌ์—ญ(์˜ˆ: ๊ธ€ ๋ชฉ๋ก)์—์„œ ์„œ๋ฒ„ ์—๋Ÿฌ๊ฐ€ ์˜ค๋ฅ˜๊ฐ€ ๋‚˜๋„, ํ”„๋กœํ•„ ๋ Œ๋”๋ง ๋ฐ•์Šค๋Š” ๋„๋–ก์—†์ด ์‚ด์•„๋‚จ์Šต๋‹ˆ๋‹ค. ํ™”์ดํŠธ์•„์›ƒ(์•ฑ ์ „์ฒด๊ฐ€ ํ•˜์–—๊ฒŒ ์ค‘๋‹จ๋˜๋Š” ํ˜„์ƒ)์„ ์™„๋ฒฝํžˆ ๋ฐฉ์–ดํ•˜๋Š” ๋ถ€๋ถ„ ์žฅ์•  ๊ฒฉ๋ฆฌ ์‹œ์Šคํ…œ์ด ์™„์„ฑ๋œ ๊ฒƒ์ž…๋‹ˆ๋‹ค.


๐ŸŒŸ (์‹ฌํ™” ๋ณด๋„ˆ์Šค) Next.js App Router์™€์˜ ๋งŒ๋‚จ

Next.js์˜ ์‹ ํ˜• App Router(app/ ๋””๋ ‰ํ† ๋ฆฌ)๋Š” ์ด Suspense ์™€ ErrorBoundary ์˜ ๊ฐœ๋…์„ ์šฐ์ฃผ ๋๊นŒ์ง€ ํŒŒ์ผ ์‹œ์Šคํ…œ์œผ๋กœ ๋Œ์–ด์˜ฌ๋ ธ์Šต๋‹ˆ๋‹ค.

  • Next.js์—์„œ loading.tsx ํŒŒ์ผ์„ ๋งŒ๋“ค๋ฉด? ๐Ÿ‘‰ ์ž๋™์œผ๋กœ ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด layout.tsx ๋ฐ‘์— <Suspense fallback={<Loading />}> ๊ป๋ฐ๊ธฐ๋ฅผ ๋ชฐ๋ž˜ ์”Œ์›Œ์ค๋‹ˆ๋‹ค!
  • error.tsx ํŒŒ์ผ์„ ๋งŒ๋“ค๋ฉด? ๐Ÿ‘‰ ๋ชฐ๋ž˜ <ErrorBoundary fallback={<Error />}> ๊ป๋ฐ๊ธฐ๋ฅผ ์”Œ์›Œ์ค๋‹ˆ๋‹ค!

์šฐ๋ฆฌ๊ฐ€ ์—ฌํƒœ ์†์œผ๋กœ ์งœ๋˜ ๋ณต์žกํ•œ ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ ๋ž˜ํ•‘ ์ฝ”๋“œ๋ฅผ, ๊ทธ์ € ํŒŒ์ผ์„ ์•Œ๋งž์€ ์œ„์น˜์— ๋ฐฐ์น˜ํ•˜๋Š” ๊ฒƒ๋งŒ์œผ๋กœ ๋ผ์šฐํŒ… ์ˆ˜์ค€์—์„œ ๋Œ€ํ–‰ํ•ด ์ฃผ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. (์ด ๊ธฐ๋ฒ•๋“ค์ด ๋“ฑ์žฅํ•œ ๊ทผ๋ณธ ์›๋ฆฌ๋ฅผ ์•Œ๋ฉด, Next.js ๊ณต์‹ ๋ฌธ์„œ๊ฐ€ ์–ผ๋งˆ๋‚˜ ํŽธ์˜์„ฑ์„ ๊ทน๋Œ€ํ™”ํ–ˆ๋Š”์ง€ ์„ ๋ช…ํ•˜๊ฒŒ ์ฒด๊ฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.)


๐Ÿ ์ด๋ฒˆ์— ๋ฐฐ์šด ๋‚ด์šฉ ์ด์ •๋ฆฌ

๊ด€์ ๋ช…๋ นํ˜• ํŒจ๋Ÿฌ๋‹ค์ž„ (if (loading))์„ ์–ธ์  ํŒจ๋Ÿฌ๋‹ค์ž„ (Suspense)
์—๋Ÿฌ/๋กœ๋”ฉ ์ฒ˜๋ฆฌ ์ฃผ์ฒด์ž์‹ ์ปดํฌ๋„ŒํŠธ ๋ณธ์ธ๋‚˜๋ฅผ ๊ฐ์‹ธ๊ณ  ์žˆ๋Š” ๋ถ€๋ชจ ์ปจํ…Œ์ด๋„ˆ ๊ณ„์ธต
์ฝ”๋“œ์˜ ๊ด€์‹ฌ์‚ฌ๋กœ์ง(Fetch) + ๋ฐฉ์–ด๋ง‰(If๋ฌธ) + ๊ตฌ์กฐ(UI) ๋’ค์„ž์ž„์˜ค์ง ํ•ต์‹ฌ ๋ฐ์ดํ„ฐ UI ๋ Œ๋”๋ง ํ•˜๋‚˜์—๋งŒ ์ง‘์ค‘
ํƒ€์ž… ์•ˆ์ •์„ฑ์Šคํฌ๋ฆฐ์— ๋œฐ ๋•Œ๊นŒ์ง€ ๋ฐ์ดํ„ฐ๊ฐ€ null์ผ ์ˆ˜ ์žˆ์Œ๋ Œ๋”๋ง ๋˜๋Š” ์ˆœ๊ฐ„ ๋ฐ์ดํ„ฐ๋Š” ๋ฌด์กฐ๊ฑด ์กด์žฌํ•จ 100% ๋ณด์žฅ
๋ ˆ์ด์•„์›ƒ ์ž์œ ๋„๊ธฐํš์ด ๋ฐ”๋€Œ๋ฉด ๋กœ์ง ์ฝ”๋“œ ์ „์ฒด๋ฅผ ๊ฐˆ์•„์—Ž์–ด์•ผ ํ•จ์ปดํฌ๋„ŒํŠธ๋Š” ๋†”๋‘๊ณ  ๋ฐ”๊นฅ Suspense ์œ„์น˜๋งŒ ์กฐ๋ฆฝํ•˜๋ฉด ๋จ

๐Ÿ’ก ํ•œ ์ค„๋กœ ๊ธฐ์–ตํ•˜๊ธฐ
"์ปดํฌ๋„ŒํŠธ๋Š” ๋ˆˆ๋ฌผ์„ ํ˜๋ฆฌ์ง€ ์•Š๋Š”๋‹ค."
๋กœ๋”ฉ๊ณผ ์—๋Ÿฌ๋Š” ๋‚ด๋ถ€์—์„œ ์ œ๊ฐ๊ฐ ์ฒ˜๋ฆฌํ•˜์ง€ ๋ง๊ณ  ๋ช…์‹œ์ ์œผ๋กœ ์ƒ์œ„ ๊ฒฝ๊ณ„๋กœ ์˜ฌ๋ ค๋ผ(Throw). Suspense์™€ ErrorBoundary๊ฐ€ ์‚ฌ์šฉ์ž๊ฐ€ ๋ณด๋Š” ํ™”๋ฉด ๋‹จ์œ„๋กœ ์•ˆ์ •์ ์ธ ๊ฒฝ๊ณ„๋ฅผ ๋งŒ๋“ค์–ด ์ค„ ๊ฒƒ์ด๋‹ค.


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

Q1. Suspense์™€ ErrorBoundary๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ์„ ์–ธ์ ์œผ๋กœ ๋ถ„๋ฆฌํ–ˆ์„ ๋•Œ, ์ž์‹ ์ปดํฌ๋„ŒํŠธ(์š”๋ฆฌ์‚ฌ)์˜ ๋‚ด๋ถ€ ์ฝ”๋“œ ์งˆ๋Ÿ‰์ด ๊ทน๋‹จ์ ์œผ๋กœ ๊ฐ€๋ฒผ์›Œ์ง€๋Š” ์•„ํ‚คํ…์ฒ˜์  ์ด์œ ๋Š” ๋ฌด์—‡์ธ๊ฐ€์š”?

  • A) ํด๋ž˜์Šค ์ปดํฌ๋„ŒํŠธ์˜ LifeCycle ๋ฉ”์„œ๋“œ๋ฅผ Hooks๋กœ ์˜ค๋ฒ„๋ผ์ด๋”ฉํ•˜๋ฉด์„œ ์ฝœ๋ฐฑ ๊ณผ๋ฐ€์ด ํŽด์ง€๊ธฐ ๋•Œ๋ฌธ.
  • B) ์ž์‹ ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€์— ์กด์žฌํ•˜๋˜ isLoading, isError ์ƒํƒœ ๋ณ€์ˆ˜์™€ ๊ทธ์— ๋”ฐ๋ฅธ ๋ฐฉํŒจ๋ง‰์ด ๋ถ„๊ธฐ(if) ์ฝ”๋“œ๋“ค์ด ํ†ต์งธ๋กœ ๋œฏ๊ฒจ๋‚˜๊ฐ€๋ฉฐ ์ปดํฌ๋„ŒํŠธ์˜ ์œ ์ผํ•œ ์—ญํ• ์ธ '๋ฐ์ดํ„ฐ ์กด์žฌ ์‹œ์˜ UI ๋งคํ•‘'๋งŒ ์ˆœ์ˆ˜ํ•˜๊ฒŒ ๋‚จ๊ธฐ ๋•Œ๋ฌธ.
  • C) Suspense ๊ตฌ๋ฌธ ๋‚ด๋ถ€์˜ ์ž์‹๋“ค์€ React ๊ฐ€์ƒ ๋”(VDOM) ๋ณ€ํ™˜์„ ๊ฑฐ์น˜์ง€ ์•Š๊ณ  ์ง์ ‘ ๋ธŒ๋ผ์šฐ์ € ๋„ค์ดํ‹ฐ๋ธŒ ์ฝ”๋“œ๋กœ C++ ์ปดํŒŒ์ผ๋˜์–ด ๊ทธ๋ ค์ง€๊ธฐ ๋•Œ๋ฌธ.

โœ… ์ •๋‹ต: B

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค: ์ •ํ™•ํ•ฉ๋‹ˆ๋‹ค! ๊ธฐ์กด ํŒจ๋Ÿฌ๋‹ค์ž„์—์„œ ๊ฐœ๋ฐœ์ž๋Š” ํ•œ ์ง€๋ถ• ํ•œ ๊ฐ€์กฑ ์•ˆ์—์„œ "์•„์ง ์˜ค์ง€ ์•Š์€ ๋ฐ์ดํ„ฐ", "๋ง๊ฐ€์ง„ ๋ฐ์ดํ„ฐ", "์ •์ƒ์ ์ธ ๋ฐ์ดํ„ฐ" 3๊ฐ€์ง€ ํ‰ํ–‰์šฐ์ฃผ๋ฅผ ๋ชจ๋‘ if-else ๋ฌธ์œผ๋กœ ์ผ€์–ดํ•ด์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ฑ…์ž„์„ ์œ„์ž„(Throw)ํ•จ์œผ๋กœ์จ ์ปดํฌ๋„ŒํŠธ๋Š” ์˜ค์ง "๋ฐ์ดํ„ฐ๊ฐ€ ๋„์ฐฉํ•œ ์•„๋ฆ„๋‹ค์šด 1๊ฐœ ์šฐ์ฃผ"์˜ ์–ผ๊ตดํ‘œ์ •๋งŒ ๊ทธ๋ฆด ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ๊ณ , ํฐ ๊ฐ€๋…์„ฑ ์ƒ์Šน์„ ์ด๋ค„๋ƒˆ์Šต๋‹ˆ๋‹ค.

Q2. ์‚ฌ๋‚ด ํ”„๋กœ์ ํŠธ์—์„œ ๋งˆ์ดํŽ˜์ด์ง€๋ฅผ ๋ฆฌํŒฉํ† ๋ง ์ค‘์ž…๋‹ˆ๋‹ค. ์œ ์ € ํ”„๋กœํ•„ ์„น์…˜๊ณผ ๊ฒฐ์ œ ๋‚ด์—ญ ์„น์…˜, ๊ทธ๋ฆฌ๊ณ  ์ถ”์ฒœ ์ƒํ’ˆ 3๊ฐœ์˜ ํŒŒํŠธ๊ฐ€ ๋ชจ๋‘ ๋…๋ฆฝ์ ์ธ ์™ธ๋ถ€ API๋ฅผ ๊ธ์–ด์˜ค๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ ์ค‘ Suspense์™€ ErrorBoundary๋ฅผ ์„ค๊ณ„ํ•˜๋Š” ์‹œ๋‹ˆ์–ด์˜ ๊ฐ€์žฅ ๋ฐ”๋žŒ์งํ•œ ์‹œ์•ผ๋Š”?

  • A) 3๊ฐœ์˜ ์„น์…˜์„ ์ „๋ถ€ ํ•˜๋‚˜์˜ ๊ฑฐ๋Œ€ํ•œ <Suspense>์™€ <ErrorBoundary>๋กœ ์ตœ์ƒ๋‹จ ๋ ˆ์ด์•„์›ƒ์— ๋ชฝ๋•… ๋ฌถ์–ด์„œ ๋‹จ์ผ ์‹œ์Šคํ…œ์œผ๋กœ ๋งŒ๋“ ๋‹ค. (ํ•˜๋‚˜๋ผ๋„ ๋กœ๋”ฉ ์ค‘์ด๋ฉด ํ™”๋ฉด ์ „์ฒด ํŒฝ์ด ๋Œ๋ฆฌ๊ธฐ)
  • B) ๊ฐ 3๊ฐœ์˜ ์ปดํฌ๋„ŒํŠธ ํŒŒ์ผ ์•ˆ์— ์ง์ ‘ ๋“ค์–ด๊ฐ€์„œ catch ๊ตฌ๋ฌธ์„ ํŒŒ๊ณ , ์Šคํ”ผ๋„ˆ ๋„์šฐ๋Š” if ์ฝ”๋“œ๋ฅผ ์šฑ์—ฌ๋„ฃ์–ด ์ž๊ธ‰์ž์กฑํ•˜๊ฒŒ ํ•œ๋‹ค.
  • C) ๊ฐ ์„น์…˜์„ ๊ฐ์‹ธ๋Š” <Suspense> 3๊ฐœ์™€ <ErrorBoundary> 3๊ฐœ๋ฅผ ๋ถ€๋ชจ ๋ ˆ์ด์•„์›ƒ์—์„œ ๊ฐ๊ฐ ๋…๋ฆฝ(๋ณ‘๋ ฌ)์ ์œผ๋กœ ์”Œ์›Œ์ค€๋‹ค. (ํ”„๋กœํ•„์€ ์ผ์ฐ ๋œจ๋ฉด ๋จผ์ € ๋ณด์—ฌ์ฃผ๊ณ , ๊ฒฐ์ œ ๋‚ด์—ญ์ด DB ์—ฐ๊ฒฐ ๋ฌธ์ œ๋กœ ์‹คํŒจํ•˜๋”๋ผ๋„ ๋‚˜๋จธ์ง€ ํŽ˜์ด์ง€๋Š” ์ •์ƒ ๋ Œ๋”๋ง ๋˜๋„๋ก ์ปดํฌ๋„ŒํŠธ ์„ฌ(Island)์„ ๊ตฌ์ถ•ํ•œ๋‹ค)

โœ… **์ •๋‹ต:**C

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค: ๋งˆ์ดํฌ๋กœ ์•„ํ‚คํ…์ฒ˜์˜ ์ •์ˆ˜์ž…๋‹ˆ๋‹ค. A ๋ฐฉ์‹์€ ๊ฒฐ์ œ ๋‚ด์—ญ API ์‘๋‹ต์ด ํ˜ผ์ž 5์ดˆ ๊ฑธ๋ฆด ๊ฒฝ์šฐ, ์ด๋ฏธ 0.1์ดˆ ๋งŒ์— ๋„์ฐฉํ•œ ์œ ์ € ํ”„๋กœํ•„์กฐ์ฐจ ์‚ฌ์šฉ์ž ๋ˆˆ์•ž์— ์•ˆ ๋ณด์—ฌ์ฃผ๋Š” ์œ„ํ—˜ํ•œ ์ฒด๊ฐ ๋ณ‘๋ชฉ(Waterfall bottleneck) ์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ๋ฐ˜๋ฉด C ๋ฐฉ์‹์ฒ˜๋Ÿผ ์ž‘๊ฒŒ ๊ฒฉ๋ฆฌ(Colocated Boundary)์‹œํ‚ค๋ฉด ๊ฐ ๊ตฌ์—ญ์ด ์ž๊ธฐ ํŽ˜์ด์Šค๋Œ€๋กœ ๋กœ๋”ฉ ํŒฝ์ด๋ฅผ ๋Œ๋ฆฌ๋‹ค ๋ Œ๋”๋ง๋˜๋ฉฐ, ํ•˜๋‚˜๊ฐ€ ์˜ค๋ฅ˜๊ฐ€ ๋‚˜๋„ ๋‚˜๋จธ์ง€ 2๊ฐœ๋Š” ํ™”๋ฉด์— ์‚ด์•„๋‚จ๋Š” ๊ฐ•์ธํ•œ ํƒ„๋ ฅ์„ฑ(Resilience) ์„ ํ™•๋ณดํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

Q3. ์˜์ฒ ์ด์˜ ํ…Œ์ŠคํŠธ ํƒ€์ž„: ์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ ํ”„๋กœํ•„ ํŽ˜์ด์ง€์—์„œ ProfileCard, RecentPosts, BadgePanel์ด ๊ฐ๊ฐ ๋‹ค๋ฅธ API๋ฅผ Suspense ๊ธฐ๋ฐ˜ ํ›…์œผ๋กœ ์ฝ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์˜์ˆ™์ด "ํ”„๋กœํ•„ ์‚ฌ์ง„์€ ๋จผ์ € ๋ณด์ด๊ณ , ์ž‘์„ฑ ๊ธ€ ๋ชฉ๋ก๋งŒ ์‹คํŒจํ•˜๋ฉด ๊ทธ ์˜์—ญ์— ์žฌ์‹œ๋„ ๋ฒ„ํŠผ์„ ๋ณด์—ฌ์ฃผ์„ธ์š”"๋ผ๊ณ  ์š”์ฒญํ–ˆ์Šต๋‹ˆ๋‹ค. ์˜ํ˜ธ๊ฐ€ ๋ฆฌ๋ทฐ์—์„œ ํ†ต๊ณผ์‹œํ‚ฌ ์„ค๊ณ„๋Š” ๋ฌด์—‡์ธ๊ฐ€์š”?

  • A) ์„ธ ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ•˜๋‚˜์˜ ์ตœ์ƒ์œ„ <Suspense>์™€ ํ•˜๋‚˜์˜ <ErrorBoundary>๋กœ ๊ฐ์‹ธ์„œ ํ™”๋ฉด ์ „์ฒด๊ฐ€ ํ•œ ๋ฒˆ์— ์„ฑ๊ณตํ•˜๊ฑฐ๋‚˜ ํ•œ ๋ฒˆ์— ์‹คํŒจํ•˜๊ฒŒ ๋งŒ๋“ ๋‹ค.
  • B) ๊ฐ ๋ฐ์ดํ„ฐ ์„น์…˜์„ ํ•„์š”ํ•œ UX ๋‹จ์œ„๋Œ€๋กœ ๋ณ„๋„์˜ <Suspense>์™€ <ErrorBoundary>๋กœ ๊ฐ์‹ธ๊ณ , ์‹คํŒจํ•œ ์˜์—ญ์—๋งŒ fallback๊ณผ ์žฌ์‹œ๋„ UI๋ฅผ ๋ฐฐ์น˜ํ•œ๋‹ค.
  • C) RecentPosts ๋‚ด๋ถ€์—์„œ try/catch์™€ isLoading ์ƒํƒœ๋ฅผ ๋‹ค์‹œ ๋งŒ๋“ค๊ณ , ๋‚˜๋จธ์ง€ ์ปดํฌ๋„ŒํŠธ๋Š” Suspense๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค.

โœ… ์ •๋‹ต: B

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค: Suspense์™€ ErrorBoundary์˜ ํ•ต์‹ฌ์€ ๋กœ๋”ฉ๊ณผ ์‹คํŒจ๋ฅผ "์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€ ์กฐ๊ฑด๋ฌธ"์ด ์•„๋‹ˆ๋ผ "์‚ฌ์šฉ์ž๊ฐ€ ๊ฒฝํ—˜ํ•˜๋Š” ํ™”๋ฉด ๊ตฌ์—ญ" ๋‹จ์œ„๋กœ ๊ฒฉ๋ฆฌํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ตœ์ƒ์œ„ ํ•˜๋‚˜๋กœ ๋ฌถ์œผ๋ฉด ์ž‘์€ ์‹คํŒจ๊ฐ€ ์ „์ฒด ํŽ˜์ด์ง€์˜ ํ™”์ดํŠธ์•„์›ƒ์ด ๋˜๊ณ , ์ž์‹ ๋‚ด๋ถ€๋กœ ๋˜๋Œ๋ฆฌ๋ฉด ์„ ์–ธ์  ๋น„๋™๊ธฐ ๊ตฌ์กฐ์˜ ์žฅ์ ์„ ์žƒ์Šต๋‹ˆ๋‹ค. ์˜ํ˜ธ๋ผ๋ฉด API ๋‹จ์œ„๊ฐ€ ์•„๋‹ˆ๋ผ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋…๋ฆฝ์ ์œผ๋กœ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ๋Š” UX ๋‹จ์œ„๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๊ฒฝ๊ณ„๋ฅผ ๋‚˜๋ˆ„๋ผ๊ณ  ๋ณผ ๊ฒ๋‹ˆ๋‹ค.

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

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

๐Ÿ’ก "Suspense์™€ ErrorBoundary๋Š” ์Šคํ”ผ๋„ˆ ์žฅ์‹์ด ์•„๋‹ˆ๋ผ, ๋กœ๋”ฉ๊ณผ ์‹คํŒจ๋ฅผ ์–ด๋А UX ๊ฒฝ๊ณ„์—์„œ ๊ฒฉ๋ฆฌํ• ์ง€ ๊ฒฐ์ •ํ•˜๋Š” ์„ค๊ณ„ ๋„๊ตฌ๋‹ค."

์˜ํ˜ธ ๋ฆฌ๋“œ ๋‹˜์ด "API ๊ฐœ์ˆ˜ ๋ง๊ณ  ์‚ฌ์šฉ์ž๊ฐ€ ๋”ฐ๋กœ ๋ณผ ์ˆ˜ ์žˆ๋Š” ํ™”๋ฉด ๋‹จ์œ„๋กœ ๊ฒฝ๊ณ„๋ฅผ ์žก์•„๋ณด๋ผ"๊ณ  ํ–ˆ๋˜ ๋ง์ด ๋‚จ์•˜๋‹ค. ๋‚ด์ผ๋ถ€ํ„ฐ๋Š” ์ƒˆ ๋น„๋™๊ธฐ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค ๋•Œ ๋จผ์ € ์„ฑ๊ณต UI, ๋กœ๋”ฉ fallback, ์‹คํŒจ fallback, ์žฌ์‹œ๋„ ์œ„์น˜๋ฅผ ํ‘œ๋กœ ์ ๊ณ  ๋‚˜์„œ ์ฝ”๋“œ๋ฅผ ์งค ์ƒ๊ฐ์ด๋‹ค. ์ด์ œ ์˜์ฒ ์€ ์กฐ๊ฑด๋ฌธ์„ ์ค„์ด๋Š” ์‚ฌ๋žŒ์ด ์•„๋‹ˆ๋ผ, ์žฅ์• ๊ฐ€ ๋ฒˆ์ง€์ง€ ์•Š๋Š” ํ™”๋ฉด ๊ฒฝ๊ณ„๋ฅผ ์„ค๊ณ„ํ•˜๋Š” ์‚ฌ๋žŒ์ด ๋˜์–ด์•ผ ํ•œ๋‹ค.