๐Ÿ›ก๏ธ Next.js 8์žฅ: Error Handling โ€” ๋‚ด ์•ฑ์„ ์ง€ํ‚ค๋Š” ์ตœํ›„์˜ ๋ฐฉ์–ด์„ 

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

๐Ÿ“‹ ๊ฐœ์š”

error.tsx, not-found.tsx, ErrorBoundary๋ฅผ ์กฐํ•ฉํ•ด ์•ฑ์„ ์•ˆ์ „ํ•˜๊ฒŒ ์ง€ํ‚ค๋Š” ๋ฐฉ๋ฒ•์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

๐Ÿ“‹ ๋ชฉ์ฐจ


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

โฑ๏ธ ์˜ˆ์ƒ ์ฝ๊ธฐ ์‹œ๊ฐ„: 15๋ถ„(์ „์ฒด) / ํ•ต์‹ฌ ํŒŒํŠธ๋งŒ: 8๋ถ„

๐Ÿ—บ๏ธ ์ด ๋ฌธ์„œ์˜ ํ๋ฆ„
RSC ์—๋Ÿฌ ์—๋Ÿฌ ์ „ํŒŒ์˜ ์œ„ํ—˜์„ฑ โ†’ error.tsx ๋ฐฉ์–ด๋ง‰ ์›๋ฆฌ โ†’ ์—๋Ÿฌ๋ฅผ ์ž˜๊ฒŒ ๋ถ€์ˆ˜์–ด ๊ฒฉ๋ฆฌํ•˜๋Š” ์‹œ๋‹ˆ์–ด ์•„ํ‚คํ…์ฒ˜

๐ŸŽฏ ์ด ๋ฌธ์„œ๋ฅผ ๋‹ค ์ฝ์œผ๋ฉด ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ

  • try-catch ๋กœ๋งŒ ๊ณผ์ž‰ ์ ์šฉํ•˜๋˜ ์ทจ์•ฝํ•œ ์ฝ”๋“œ์—์„œ ๋ฒ—์–ด๋‚˜, ์„ ์–ธํ˜• ์ง€์‹œ์ž ํŒŒ์ผ(error.tsx) ํ•˜๋‚˜๋กœ ํด๋” ๋‹จ์œ„ ์—๋Ÿฌ๋ฅผ ์šฐ์•„ํ•˜๊ฒŒ ์ œ์••ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์—๋Ÿฌ๊ฐ€ ๋‚œ ๊ณณ๋งŒ ๋ฒ„ํŠผ์œผ๋กœ reset() ํ•จ์ˆ˜๋ฅผ ๋ถˆ๋ ค ํŽ˜์ด์ง€ ๊ฐ•์ œ ์ƒˆ๋กœ๊ณ ์นจ ์—†์ด ํ•ด๋‹น ํŒŒํŽธ ์ปดํฌ๋„ŒํŠธ๋งŒ ํšŒ๋ณต์‹œํ‚ค๋Š” ๊ณ ๊ธ‰ ๋ Œ๋” ์ปจํŠธ๋กค์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.

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

  • ์˜์ฒ (์ƒˆ๋กœ ์˜จ ์ฃผ๋‹ˆ์–ด): "์–ด? ์‚ฌ์ด๋“œ๋ฐ”์—์„œ ์œ ์ € ๋žญํ‚น ๊ฐ€์ ธ์˜ค๋Š” API ํ•˜๋‚˜ ํ„ฐ์กŒ๋‹ค๊ณ  ์‚ฌ์ดํŠธ ์ „์ฒด๊ฐ€ ์—„์ฒญ ๋ฌด์„œ์šด ์—๋Ÿฌ ์Šคํฌ๋ฆฐ์œผ๋กœ ๋„๋ฐฐ๋˜๋ฉด์„œ ๋ป—์–ด๋ฒ„๋ ค์š”! ๋ฉ€์ฉกํ•œ ๋‹ค๋ฅธ ๊ฒŒ์‹œํŒ ๊ธ€๋“ค๋„ ์•ˆ ๋ณด์ด๊ณ  ์™„์ „ ๋งˆ๋น„์˜ˆ์š”! ๐Ÿ˜ญ"
  • ์˜ํ˜ธ(FE ๋ฆฌ๋“œ): "์˜์ฒ  ๋‹˜... React ํŠธ๋ฆฌ๋Š” ์ปดํฌ๋„ŒํŠธ ํ•˜๋‚˜๊ฐ€ ์—๋Ÿฌ๋กœ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์ž๊ธฐ ๋ถ€๋ชจ ํ˜•์ œ๊นŒ์ง€ ๋‹ค ์ค‘๋‹จ๋˜๋Š” ์—ฐ์‡„ ์ค‘๋‹จ ๋ฒ•์น™์ด ์žˆ๋‹จ ๋ง์ด์—์š”! ๋„ฅ์ŠคํŠธ๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๋ฐฉํ™”๋ฒฝ(error.tsx) ํŒŒ์ผ๋งŒ ํˆญ ๋˜์ ธ๋†จ์–ด๋„ ๋žญํ‚น ์˜์—ญ ํ•˜๋‚˜๋งŒ ์šฐ์•„ํ•˜๊ฒŒ ์ค‘๋‹จ๋˜๊ณ  ๋‹ค๋ฅธ ์˜์—ญ์€ ๊ณ„์† ๋™์ž‘ํ–ˆ์„ ํ…๋ฐ!"

๐Ÿค” ์™œ ์•Œ์•„์•ผ ํ•˜๋Š”๊ฐ€

์˜ค๋ž˜๋œ SPA ๋ฐฉ์‹์—์„œ๋Š” API ์—๋Ÿฌ๊ฐ€ ๋‚˜๋ฉด ์ฃผ๋กœ catch ์•ˆ์—์„œ alert("์—๋Ÿฌ์ž…๋‹ˆ๋‹ค") ๋„์šฐ๊ณ  ๋๋‚ฌ์–ด.
ํ•˜์ง€๋งŒ Next.js์˜ ๋Œ€์„ธ์ธ React Server Component(RSC) ์„ธ์ƒ์€ ์™„์ „ํžˆ ๋‹ฌ๋ผ.
์„œ๋ฒ„์—์„œ ์กฐ๋ฆฝ๋˜๋˜ HTML ์ชผ๊ฐ€๋ฆฌ๋“ค์ด await db() ํ•˜๋‹ค๊ฐ€ 1์ดˆ ๋’ค์— ํŽ‘! ํ•˜๊ณ  ์˜ค๋ฅ˜๊ฐ€ ๋‚˜๋ฒ„๋ ค(Exception Throw).

Next.js ์„œ๋ฒ„๊ฐ€ ์—๋Ÿฌ๋ฅผ ๋ฟœ์œผ๋ฉด ์ด ์—๋Ÿฌ๊ฐ€ ๊ฑท์žก์„ ์ˆ˜ ์—†์ด ์ƒ์œ„ ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ๋กœ ์—ญ๋ฅ˜ํ•˜๋ฉฐ ์˜ฌ๋ผ๊ฐ€. ๋‹จ๋‹จํ•œ ๋šœ๊ป‘(Error Boundary)์œผ๋กœ ์–ด๋А ์ค‘๊ฐ„ ์ง€์ ์—์„œ ๋”ฑ! ๋ง‰์•„๋‚ด์ง€ ์•Š์œผ๋ฉด, ๊ฒฐ๊ตญ ์ตœ์ƒ๋‹จ ๋ฃจํŠธ ๋ ˆ์ด์•„์›ƒ๋งˆ์ € ์ค‘๋‹จ์‹œํ‚ค๋ฉฐ ์•ฑ ์ „์ฒด๊ฐ€ ์ƒˆํ•˜์–€ "Internal Server Error" ํ™”๋ฉด์œผ๋กœ ๋Œ๋ณ€ํ•ด.
๊ทธ ์ž‘์€ ์ฐŒ๊บผ๊ธฐ ์—๋Ÿฌ ํ•˜๋‚˜ ๋•Œ๋ฌธ์— ๋ฉ€์ฉกํ•˜๊ฒŒ ๋Œ์•„๊ฐ€๋˜ ์‚ฌ์ดํŠธ ๋ฉ”์ธ GNB์™€ ์‚ฌ์ด๋“œํƒญ๋งˆ์ € ์ฆ๋ฐœํ•ด๋ฒ„๋ฆฌ๋Š” ๊ฒƒ์€ ์œ„ํ—˜ํ•œ UX(์‚ฌ์šฉ์ž ๊ฒฝํ—˜)์•ผ.

์ด ๊ฑฐ๋Œ€ํ•œ ๋„๋ฏธ๋…ธ ๋ถ•๊ดด๋ฅผ ๋ง‰์•„๋‚ด๋Š” ๋„ฅ์ŠคํŠธ ํŒŒ์ผ ์‹œ์Šคํ…œ ์˜ˆ์•ฝ์–ด๊ฐ€ ๋ฐ”๋กœ error.tsx์•ผ.


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

๐Ÿง’ 5์‚ด์—๊ฒŒ ์„ค๋ช…ํ•œ๋‹ค๋ฉด? (์˜์ˆ˜๋„ค ๊ตญ๋ฐฅ์ง‘์˜ ๋ฐฉํ™” ์…”ํ„ฐ)

โŒ ๊ณผ๊ฑฐ (์˜์ฒ ์ด์˜ ์นธ๋ง‰์ด ์—†๋Š” ์‹๋‹น)
์ฃผ๋ฐฉ ํ™”๊ตฌ(API) ์ค‘ ํ•˜๋‚˜๊ฐ€ ๊ณผ์—ด๋˜์–ด ๋ถˆ์ด ๋‚ฌ์–ด. ๊ทธ๋Ÿฐ๋ฐ ๋ฐฉ์ˆ˜ ๊ฒฉ๋ฒฝ์ด๋‚˜ ์นธ๋ง‰์ด๊ฐ€ ํ•˜๋‚˜๋„ ์—†๋„ค?
๋ถˆ๊ธธ์ด ํ™”์žฅ์‹ค์„ ๋„˜์–ด ๋ณต๋„๋กœ ๊ฐ€๊ณ , ์นด์šดํ„ฐ๊นŒ์ง€ ๋ฒˆ์ง€๋”๋‹ˆ ์ˆœ์‹๊ฐ„์— ์‹๋‹น ์ „์ฒด(์•ฑ ์ „์ฒด) ๊ฐ€ ํƒ€๋ฒ„๋ ค ๋ฉธ๋งํ•ด.

โœ… ํ˜„์žฌ (error.tsx ์„ ์–ธํ˜• ๋ฐฉํ™” ์…”ํ„ฐ)
์ฃผ๋ฐฉ์žฅ ์˜ํ˜ธ ๋‹˜์ด ์ฃผ๋ฐฉ ๊ตฌ์—ญ๋งˆ๋‹ค, ๊ทธ๋ฆฌ๊ณ  ๋ณต๋„ ์ž…๊ตฌ๋งˆ๋‹ค ํŠผํŠผํ•œ ์ฒ ์ œ ์…”ํ„ฐ(๋ฐฉํ™” ๊ฒฉ๋ฒฝ = error.tsx) ๋ฅผ ํ•˜๋‚˜์”ฉ ์„ค์น˜ํ•ด๋’€์–ด.
์ฃผ๋ฐฉ์—์„œ ๋ถˆ์ด ๋‚˜๋ฉด ์‚์šฉ์‚์šฉ! ์†Œ๋ฆฌ์™€ ํ•จ๊ป˜ ๊ทธ ์ฆ‰์‹œ ์ œ์ผ ๊ฐ€๊นŒ์šด ์…”ํ„ฐ๊ฐ€ ์ฒ ์ปน! ํ•˜๊ณ  ๋‚ด๋ ค๊ฐ€์„œ ๋ถˆ๊ธธ์„ ๊ฐ€๋‘ฌ๋ฒ„๋ ค.
์ฃผ๋ฐฉ ์ผ๋ถ€ ๊ตฌ์—ญ์€ ์ˆ˜๋ฆฌํ•˜๋А๋ผ ๋ชป ์“ฐ๊ฒ ์ง€๋งŒ(fallback ์—๋Ÿฌ UI) , ํ™€์—์„œ ๊ตญ๋ฐฅ์„ ๋จน๋˜ ์†๋‹˜๋“ค์€ ์•„๋ฌด ์ผ ์—†๋‹ค๋Š” ๋“ฏ ๋ง›์žˆ๊ฒŒ ์‹์‚ฌ๋ฅผ ๊ณ„์†ํ•  ์ˆ˜ ์žˆ์–ด!


๐Ÿงฉ error.tsx์˜ ๋งˆ๋ฒ•: Error Boundary์˜ ์ง„ํ™” ๐ŸŸข

๊ธฐ์กด React ๊ฐœ๋ฐœ์ž๋“ค์ด Error Boundary ๋žฉํ•‘๊ธฐ(Class Component)๋ฅผ ์†์ˆ˜ ์งœ๋ฉฐ ๊ณ ํ†ต๋ฐ›์•˜๋‹ค๋ฉด, Next.js์—์„œ๋Š” ๊ทธ๋ƒฅ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•  ๊ฒƒ ๊ฐ™์€ ํด๋”์— ์ € ์ด๋ฆ„์˜ ํŒŒ์ผ์„ "๋งŒ๋“ค๊ธฐ๋งŒ ํ•˜๋ฉด" ๋ผ.

1) ๋ฐฉ์–ด๋ง‰ ๊ธฐ๋ณธ ์›๋ฆฌ

app/dashboard/ ํŽ˜์ด์ง€๊ฐ€ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜์ž.

app/
 โ””โ”€ dashboard/
     โ”œโ”€ layout.tsx  (1. ์ด ํŒŒ์ผ๋งˆ์ € ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๋” ์œ„์ชฝ์œผ๋กœ ์—ญ๋ฅ˜ํ•ด ์˜ฌ๋ผ๊ฐ€๋ฒ„๋ฆฐ๋‹ค)
     โ”œโ”€ error.tsx   (2. ๊ฐ•๋ ฅํ•œ ์—๋Ÿฌ ๊ฒฝ๊ณ„! ์ด ํŒŒ์ผ์ด ์กด์žฌํ•˜๋Š” ํ•œ page.tsx์˜ ์—๋Ÿฌ๋Š” ๋ฌธ ๋ฐ–์„ ๋‚˜๊ฐ€์ง€ ๋ชปํ•จ)
     โ””โ”€ page.tsx    (3. ์—๋Ÿฌ ์œ ๋ฐœ์ž! ๋‚ด๋ถ€์—์„œ DB ์กฐํšŒํ•˜๋‹ค ๋ป—์Œ)

๋งŒ์•ฝ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด, Next.js๊ฐ€ page.tsx UI๋ฅผ ๋ Œ๋” ๋ฐ•์Šค์—์„œ ์‹น ๋œ์–ด๋‚ด๋ฒ„๋ฆฌ๊ณ , ๊ทธ ์ž๋ฆฌ์— ๋‹ˆ๊ฐ€ error.tsx ํŒŒ์ผ์—์„œ ๋ฆฌํ„ด์‹œํ‚จ Fallback UI ์ปดํฌ๋„ŒํŠธ๋กœ ์™! ๋Œ€์ฒดํ•ด๋ฒ„๋ ค.

2) ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ ์„ ์–ธ ๊ฐ•์ œ ๊ทœ์•ฝ (use client)

โš ๏ธ ๊ฐ€์žฅ ์ดˆ๋ณด์ž๋“ค์ด ๋งŽ์ด ํ•˜๋Š” ์‹ค์ˆ˜ 1์ˆœ์œ„. error.tsx๋Š” ๋ฌด์กฐ๊ฑด ์ฒซ ์ค„์— 'use client' ์ง€์‹œ์ž ๊ฐ€ ๋‹ฌ๋ ค ์žˆ์–ด์•ผ ํ•ด.

์™œ์ผ๊นŒ? ์—๋Ÿฌ๋Š” ์–ธ์ œ ์–ด๋А ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ• ์ง€ ๋ชฐ๋ผ.
์„œ๋ฒ„์—์„œ HTML์„ ์กฐ๋ฆฝํ•˜๋‹ค๊ฐ€(RSC) ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜๋„ ์žˆ๊ณ , ๋‚ด ํฐ์—์„œ onClick ๋ˆ„๋ฅด๋‹ค ๋Ÿฐํƒ€์ž„์—(Client) ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜๋„ ์žˆ์ง€. ๋ชจ๋“  ์—๋Ÿฌ(Server, Client ๋ฌด๊ด€)๋ฅผ ์žก์•„์ฑ„๊ณ  ๋ธŒ๋ผ์šฐ์ € ์ฝ˜์†”์—์„œ Recovery(๋ณต๊ตฌ ๋ฒ„ํŠผ) ๋™์ž‘๊นŒ์ง€ ์ง€์›ํ•˜๋ ค๋ฉด ์ด ํŒŒ์ผ ์ž์ฒด๊ฐ€ ํด๋ผ์ด์–ธํŠธ ๋ฒ ์ด์Šค์—์„œ ๊ตฌ๋™๋˜์–ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด์•ผ.

// app/dashboard/error.tsx
'use client' // โค๏ธ ์žŠ์œผ๋ฉด ๋นŒ๋“œ ์—๋Ÿฌ ๋‚จ!
 
import { useEffect } from 'react'
 
export default function DashboardError({
  error,
  reset,
}: {
  error: Error & { digest?: string } // ์—๋Ÿฌ ์ •๋ณด ๊ฐ์ฒด
  reset: () => void // ์‹คํŒจํ–ˆ๋˜ page.tsx ์˜์—ญ์„ ์žฌ๋ Œ๋”๋ง ์‹œ๋„ํ•˜๋Š” ๋งˆ๋ฒ•์˜ ๋ฒ„ํŠผ
}) {
  useEffect(() => {
    // Sentry, Datadog ๋“ฑ์œผ๋กœ ๋ชฐ๋ž˜ ์—๋Ÿฌ ์ „๋ฌธ์„ ์ˆ˜์ง‘ํ•ด ์˜ฌ๋ฆฐ๋‹ค
    console.error('์•—! ๋Œ€์‹œ๋ณด๋“œ์—์„œ ์—„์ฒญ๋‚œ ์—๋Ÿฌ๊ฐ€:', error)
  }, [error])
 
  return (
    <div className="bg-red-50 p-4 rounded-md">
      <h2>๋ญ”๊ฐ€ ๋‹จ๋‹จํžˆ ์ž˜๋ชป๋์–ด์š”. (๋ฐฉ์ˆ˜ ๊ฒฉ๋ฒฝ ๋‹ซํž˜)</h2>
      {/* ๋‹ค์‹œ ์‹œ๋„ํ•˜๊ธฐ ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ, ์ปดํฌ๋„ŒํŠธ๋งŒ ๊ฐ•์ œ๋กœ ์žฌ์š”์ฒญ (์ „์ฒด ์ƒˆ๋กœ๊ณ ์นจ ๋ฌด๊ด€) */}
      <button onClick={() => reset()}>
        ๋ฌธ ๋‹ค์‹œ ํ•œ ๋ฒˆ ์—ด์–ด๋ณด๊ธฐ
      </button>
    </div>
  )
}

๐ŸŒฑ ์—๋Ÿฌ์˜ ์ง€์—ญํ™” (Localization) ์ „๋žต ๐ŸŸก

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

โŒ ์˜์ฒ ์ด์˜ ํ†ต์งœ ์—๋Ÿฌ ์ฒ˜๋ฆฌ (์ƒ์œ„ ๋ฃจํŠธ๋กœ ๋ชฝ๋•… ์—๋Ÿฌ ๋– ๋„˜๊ธฐ๊ธฐ)

์˜์ฒ ์ด๋Š” ๊ทธ๋ƒฅ ๊ท€์ฐฎ์•„์„œ ๋ฃจํŠธ ํด๋”(app/error.tsx) ํ•˜๋‚˜๋งŒ ๋งŒ๋“ค์–ด ๋’€์–ด.
๊ทธ๋ž˜์„œ ๋œ ์ค‘์š”ํ•œ ์‚ฌ์ด๋“œ๋ฐ” ์œ„์ ฏ ํ†ต์‹  ํ•˜๋‚˜ ๋ป—์—ˆ๋Š”๋ฐ, ๋ฉ”์ธ ๊ฒŒ์‹œ๊ธ€๊ณผ ํ—ค๋” ์ „์ฒด ํŠธ๋ฆฌ๊นŒ์ง€ ์ „๋ถ€ ์ƒˆํ•˜์–—๊ฒŒ ๋‚ ์•„๊ฐ€๋ฉด์„œ ์ € ์—๋Ÿฌ ๋ฐฉ๋ฒฝ ํ…์ŠคํŠธ๋งŒ ๋œ๋  ๋– ๋ฒ„๋ฆฌ๊ฒŒ ๋์ง€.

โœ… ์˜ํ˜ธ์˜ ๊ตญ์ง€์  ๋ฐฉ๋ฒฝ ์„ค๊ณ„ (ํŒŒํŽธ ์ชผ๊ฐœ๊ธฐ + Suspense ์กฐํ•ฉ)

export default function CommunityPage() {
  return (
    <article className="layout_grid">
      <MainHeader />
 
      {/* โš ๏ธ ๋ฉ”์ธ ๊ฒŒ์‹œํŒ: ๋งค์šฐ ์ค‘์š”!! ์—ฌ๊ธฐ์„œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์ „์ฒด error.tsx ๊ฐ€ ์žก๋„๋ก ๋ƒ…๋‘”๋‹ค */}
      <MainPostList />
 
      {/* ๐Ÿ›ก๏ธ ๋œ ์ค‘์š”ํ•œ ์‚ฌ์ด๋“œ๋ฐ” ์œ„์ ฏ:
          ์—ฌ๊ธฐ๋Š” ์˜ค๋ฅ˜๊ฐ€ ๋‚˜๋„ ์‚ฌ์ดํŠธ ์ด์šฉ์— ๋ฌด๊ด€ํ•˜๋ฏ€๋กœ ๋ณ„๋„์˜ ๋„๋ฉ”์ธ ํด๋”๋กœ ๋นผ์„œ ๊ฐœ๋ณ„ error.tsx ๊ด€ํ• ๊ถŒ์œผ๋กœ ๋‘๊ฑฐ๋‚˜,
          ์ˆ˜๋™ <ErrorBoundary> ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๊ตฌ์—ญ์œผ๋กœ ๋ฌถ๊ณ  fallback UI๋ฅผ ์กฐ์šฉํžˆ ๋„์›Œ ๋ฉ”์ธ ํ™”๋ฉด์„ ์‚ด๋ฆฐ๋‹ค! */}
      <aside>
        <SidebarRankingWidget />
      </aside>
 
    </article>
  )
}

๐ŸŽฏ ํ•œ ๊ฑธ์Œ ๋”: ์ปดํฌ๋„ŒํŠธ ๋‹จ์œ„์˜ ์ •๋ฐ€ ํƒ€๊ฒฉ ์—๋Ÿฌ ์ฒ˜๋ฆฌ

์˜์ฒ ์ด๋Š” ๊ถ๊ธˆํ•ด์กŒ์–ด. "์˜ํ˜ธ ๋ฆฌ๋“œ ๋‹˜, ๊ทธ๋Ÿผ error.tsx ํŒŒ์ผ ์—†์ด๋Š” ํŠน์ • ์ปดํฌ๋„ŒํŠธ ํ•˜๋‚˜๋งŒ ๋”ฑ! ์žก์•„์„œ ๊ฒฉ๋ฆฌํ•  ์ˆœ ์—†๋‚˜์š”? ํด๋”๋ฅผ ๋งค๋ฒˆ ์ƒˆ๋กœ ๋งŒ๋“ค๊ธด ์ข€ ๋ฒˆ๊ฑฐ๋กœ์›Œ์„œ์š”."

์˜ํ˜ธ ๋ฆฌ๋“œ ๋‹˜์ด ์›ƒ์œผ๋ฉฐ ๋Œ€๋‹ตํ–ˆ์ง€. "๋‹น์—ฐํžˆ ์žˆ์ฃ ! error.tsx๋Š” ๋ผ์šฐํŠธ ๋‹จ์œ„์˜ ํฐ ๊ฒฉ๋ฒฝ์ด๋ผ๋ฉด, ์šฐ๋ฆฌ๊ฐ€ ์ง์ ‘ ErrorBoundary ์ปดํฌ๋„ŒํŠธ๋กœ ๊ฐ์‹ธ๋ฉด ๊ฐœ๋ณ„ ์ปดํฌ๋„ŒํŠธ ๋‹จ์œ„๋กœ ์ •๋ฐ€ ํƒ€๊ฒฉ ๋ฐฉ์–ด๊ฐ€ ๊ฐ€๋Šฅํ•ด์š”."

1) react-error-boundary ํ™œ์šฉ (๊ถŒ์žฅ)

๊ฐ€์žฅ ์šฐ์•„ํ•œ ๋ฐฉ๋ฒ•์€ ๊ฒ€์ฆ๋œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์“ฐ๋Š” ๊ฑฐ์•ผ. ํŠน์ • ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์˜ค๋ฅ˜๊ฐ€ ๋‚˜๋„ ํŽ˜์ด์ง€ ์ „์ฒด๊ฐ€ ํ•˜์–—๊ฒŒ ๋ณ€ํ•˜๋Š” ๊ฑธ ๋ง‰์•„์ฃผ์ง€.

'use client'
 
import { ErrorBoundary } from 'react-error-boundary'
 
function ErrorFallback({ error, resetErrorBoundary }: any) {
  return (
    <div className="p-2 border border-red-200 bg-red-50 text-xs text-red-600">
      <p>์œ„์ ฏ ๋กœ๋“œ ์‹คํŒจ ๐Ÿ˜ญ</p>
      <button onClick={resetErrorBoundary} className="underline">๋‹ค์‹œ ์‹œ๋„</button>
    </div>
  )
}
 
export default function Sidebar() {
  return (
    <aside>
      <h3>์‹ค์‹œ๊ฐ„ ๋žญํ‚น</h3>
      {/* ๐Ÿ›ก๏ธ ์ด ์œ„์ ฏ ํ•˜๋‚˜๊ฐ€ ์˜ค๋ฅ˜๊ฐ€ ๋‚˜๋„ ์‚ฌ์ด๋“œ๋ฐ” ์ „์ฒด์™€ ๋ฉ”์ธ ํŽ˜์ด์ง€๋Š” ๋ฌด์‚ฌํ•ด! */}
      <ErrorBoundary
        FallbackComponent={ErrorFallback}
        onReset={() => {
          // ๋‹ค์‹œ ์‹œ๋„ํ•  ๋•Œ ์ˆ˜ํ–‰ํ•  ๋กœ์ง
        }}
      >
        <SidebarRankingWidget />
      </ErrorBoundary>
    </aside>
  )
}

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด error.tsx๊ฐ€ ๋‹ด๋‹นํ•˜๋Š” 'ํŽ˜์ด์ง€ ์ „์ฒด' ํ˜น์€ 'ํด๋” ์ „์ฒด' ๋‹จ์œ„์˜ ๊ฑฐ์‹œ์  ๋ฐฉ์–ด์™€, ์ปดํฌ๋„ŒํŠธ ๋‹จ์œ„์˜ ๋ฏธ์‹œ์  ๋ฐฉ์–ด๋ฅผ ์กฐํ™”๋กญ๊ฒŒ ์„ž์–ด์„œ "์ ˆ๋Œ€ ์‚ฌ๋ผ์ง€์ง€ ์•Š๋Š” ์•ฑ" ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด! ๐Ÿฃ
ํ•ต์‹ฌ ๋ฉ˜ํƒˆ ๋ชจ๋ธ: ์—๋Ÿฌ ๊ฒฝ๊ณ„(error.tsx)์„ ์–ด๋”” ๊ณ„์ธต(ํด๋” ๋ ˆ๋ฒจ)์— ๋‘˜ ๊ฒƒ์ธ๊ฐ€?
์—๋Ÿฌ๊ฐ€ ๋‚œ ๊ณณ์— error.tsx๊ฐ€ ์—†๋‹ค๋ฉด? ์—๋Ÿฌ ๋Œ€์ƒ๊ณผ ์—๋Ÿฌ๋Š” ์ž๊ธฐ๋ฅผ ์ฒ˜๋ฆฌํ•  ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ์ƒ์œ„ ํด๋”์˜ error.tsx๋ฅผ ๋งŒ๋‚  ๋•Œ๊นŒ์ง€ ๋ถ€๋ชจ ๋ฐฉํ–ฅ์œผ๋กœ ์ „ํŒŒ(Bubble Up) ๋œ๋‹ค.
๋”ฐ๋ผ์„œ, ์‚ด๋ ค์•ผ ํ•˜๋Š” ํ•ต์‹ฌ ๊ธฐ๋‘ฅ(Layout)์˜ ๋‚ด๋ถ€์— ๋ฐฉ๋ฒฝ์„ ์˜ค๋ฐ€์กฐ๋ฐ€ํ•˜๊ฒŒ ๊น”์•„์ฃผ๋Š” ๊ฒƒ์ด ํŒŒํŽธํ™” ์„ค๊ณ„์˜ ๊ฝƒ์ด๋‹ค!


๐Ÿšจ ์„œ๋ฒ„ ๋‹ค์šด ์ตœํ›„์˜ ๋ณด๋ฃจ: global-error.tsx ๐Ÿ”ด

์งˆ๋ฌธ ํ•˜๋‚˜ ํ•ด๋ณด์ž.
"๋งŒ์•ฝ์— ์ œ์ผ ๋ฐ”๊นฅ ๊ป๋ฐ๊ธฐ ๊ธฐ๋‘ฅ์ธ app/layout.tsx (html, body ํƒœ๊ทธ๊ฐ€ ์„ ์–ธ๋œ ๊ทธ๊ณณ!) ์ž์ฒด์—์„œ ์ฝ”๋“œ ์งœ๋‹ค๊ฐ€ ์—๋Ÿฌ๊ฐ€ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์–ด๋–กํ•ด? ์ œ์ผ ๋†’์€ ๊ณณ์ด๋‹ˆ๊นŒ ๊ฑ”๋ฅผ ๊ฐ์‹ธ์ฃผ๋Š” ๋ถ€๋ชจ(์ƒ์œ„) error.tsx๊ฐ€ ์„ธ์ƒ์— ์กด์žฌํ•˜์ง€ ์•Š์„ ํ…๋ฐ?!"

์ •๋‹ต์ด์•ผ. app/error.tsx ์กฐ์ฐจ๋„ ๋ฃจํŠธ layout.tsx์˜ ์—๋Ÿฌ๋ฅผ ๋ฐฉ์–ดํ•ด๋‚ด์ง€ ๋ชปํ•ด. (์˜ค์ง layout ๋‚ด๋ถ€์˜ ํŽ˜์ด์ง€๋“ค๋งŒ ๋ฐฉ์–ดํ•จ).
๊ฐ€์žฅ ๋ฌด๊ฑฐ์šด ์‹ , ๊ฐ€์žฅ ์ตœ์ƒ์œ„ layout.tsx์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ ์ ˆ์ฒด์ ˆ๋ช…์˜ ์—๋Ÿฌ๋ฅผ ๋‹ด์•„๋‚ด๋Š”, ๋ง ๊ทธ๋Œ€๋กœ ์ง„์งœ ์ˆจ๊ฒจ์ง„ ๋งˆ์ง€๋ง‰ ๋ธ”๋ž™๋ฐ•์Šค๊ฐ€ ์˜ˆ์•ฝ์–ด๋กœ ์กด์žฌํ•ด.

// app/global-error.tsx (์ตœ์ƒ๋‹จ ๋ฃจํŠธ ๊ตฌ์—ญ์—๋งŒ ์ƒ์„ฑ ๊ฐ€๋Šฅ)
'use client'
 
export default function GlobalError({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    // โš ๏ธ ์—ฌ๊ธด layout์ด ํ„ฐ์กŒ์„ ๋•Œ ๋Œ€์ฒด๋˜๋Š” ๊ณณ์ด๋‹ˆ๊นŒ
    // ๋‚˜ ์Šค์Šค๋กœ๊ฐ€ ๋ฐ˜๋“œ์‹œ <html> <body> ๊ป๋ฐ๊ธฐ๋ฅผ ๋งŒ๋“ค์–ด๋‚ด์•ผ๋งŒ ํ•ด!!
    <html>
      <body>
        <h1>์‹œ์Šคํ…œ ์ฝ”์–ด ๋ถ•๊ดด (Global Error ๋ฐœ๋™)</h1>
        <p>์ „๋ฉด ์žฅ์•  ์กฐ์น˜ ์ค‘์ž…๋‹ˆ๋‹ค... {error.message}</p>
        <button onClick={() => reset()}>์žฌ๋ถ€ํŒ… ์‹œ๋„</button>
      </body>
    </html>
  )
}

๐Ÿ’ก ํŒ: global-error.tsx๋Š” ํ”„๋กœ๋•์…˜ ๋นŒ๋“œ์—์„œ๋งŒ ํ™œ์„ฑํ™”๋ผ. (๊ฐœ๋ฐœ์ž ํ™˜๊ฒฝ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋Š” ํ„ฐ๋ฏธ๋„ ํŒ์—…์ด ์šฐ์„ ๋  ์ˆ˜ ์žˆ์Œ). ์˜ค์ง ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์˜ ๋งˆ์ง€๋ง‰ ๊ฒฝ๊ณ„์—์„œ ์ตœ์ƒ์œ„ ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ(layout.tsx)๊ฐ€ ๋””๋น„ ํ†ต์‹  ๋“ฑ์—์„œ ์ค‘๋‹จ๋˜์–ด๋‚˜๊ฐˆ ๋•Œ๋งŒ ํŠ€์–ด๋‚˜์˜ค๋Š” ์ตœํ›„ ๋ฐฉ์–ด์„  ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•˜์ง€.


๐Ÿ’ฅ ์—๋Ÿฌ ํ•ด๊ฒฐ ์นดํƒˆ๋กœ๊ทธ

์—๋Ÿฌ ๋ฉ”์‹œ์ง€๊ฐ€ ๋œจ๋ฉด Ctrl+F๋กœ ๊ฒ€์ƒ‰ํ•ด๋ด.

โŒ Error Components must be Client Components...

์›์ธ: error.tsx ๋‚˜ global-error.tsx ํŒŒ์ผ ์ตœ์ƒ๋‹จ์—์„œ 'use client' ์„ ์–ธ์„ ๊นŒ๋จน์—ˆ์Œ.
ํ•ด๊ฒฐ์ฑ…: 1์ดˆ ๋งŒ์— ์ตœ์ƒ๋‹จ์— 'use client' ์จ์ฃผ๊ธฐ. (๋Ÿฐํƒ€์ž„ ์—๋Ÿฌ ๋ Œ๋”๋ง์— ํ•„์ˆ˜)

โŒ ๋ฃจํŠธ layout.tsx๊ฐ€ ํ„ฐ์กŒ๋Š”๋ฐ app/error.tsx๊ฐ€ ์ž‘๋™ ์•ˆ ํ•˜๊ณ  ํ•˜์–€ ์ค‘๋‹จ ํ™”๋ฉด ๋œน๋‹ˆ๋‹ค!

์›์ธ: app/error.tsx ๊ตฌ์กฐ์ƒ ์ž์‹ ์ด ์†ํ•œ layout.tsx ํ˜•์ œ ํŒŒ์ผ์˜ ์—๋Ÿฌ๋Š” ๋ง‰์ง€ ๋ชปํ•จ (์˜ค์—ผ ๋ฒ•์น™).
ํ•ด๊ฒฐ์ฑ…: ๋ฃจํŠธ app/ ๋ ˆ๋ฒจ ๊นŠ์ด์—์„œ๋Š” layout.tsx๋ฅผ ๊ฐ์‹ธ ๋ฐฉ์–ดํ•  ์ˆ˜ ์žˆ๋Š” global-error.tsx ํŒŒ์ผ์„ ๋ณ„๋„๋กœ ๋งŒ๋“ค์–ด์•ผ ์™„๋ฒฝํžˆ ์ฐจ๋‹จ๋จ.


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

์—๋Ÿฌ ๋ฐœ์ƒ ์ปดํฌ๋„ŒํŠธ ํŒŒ์ผ์ž‘๋™ํ•˜๋Š” ๋ฐฉ์–ด ์‰ด๋“œ (์šฐ์„ ์ˆœ์œ„)์„ค๋ช…
app/dashboard/page.tsx1. app/dashboard/error.tsx๊ฐ™์€ ํด๋”(ํ˜•์ œ ๋ ˆ๋ฒจ)์˜ error.tsx๊ฐ€ ์ตœ์šฐ์„  ๊ฒฉ๋ฒฝ ๋ฐœ๋™
app/dashboard/layout.tsx1. app/error.tsx์ž๊ธฐ๊ฐ€ ํ„ฐ์กŒ์œผ๋ฏ€๋กœ, ๋‚ด ํ˜•์ œ error๋Š” ๋ฌด์šฉ์ง€๋ฌผ! ์ƒ์œ„ ๋ถ€๋ชจ ํด๋”์˜ error๊ฐ€ ์žก์•„์คŒ
app/layout.tsx (๋ฃจํŠธ)1. app/global-error.tsx์„ธ์ƒ์˜ ๋. ํ•˜๋Š˜์ด ๋ฌด๋„ˆ์กŒ์„ ๋•Œ ์ตœํ›„ ์‹ฌํŒ์ž. html ๊ป๋ฐ๊ธฐ ํ•„์ˆ˜
404 Not Found ๊ฐ•์ œ ํ˜ธ์ถœ1. app/not-found.tsx(์ด๊ฑด 9์žฅ์—์„œ ์‹ฌํ™” ํ•™์Šต) ์˜ˆ์ƒ๋œ ๋ฆฌ์†Œ์Šค ์ „์šฉ ์—†์Œ ์—๋Ÿฌ

๐Ÿ’ก ํ•œ ์ค„๋กœ ๊ธฐ์–ตํ•˜๊ธฐ
์—๋Ÿฌ๋Š” ๋ฌผ๊ณผ ๊ฐ™์•„์„œ ๋ฐฉ์ˆ˜๋ฌธ(error.tsx)์ด ์—†๋Š” ์œ—๋ฐฉ(๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ)์„ ํ–ฅํ•ด ๊ฟ€๋Ÿญ๊ฟ€๋Ÿญ ์ฐจ์˜ค๋ฅธ๋‹ค.
์ค‘์š”ํ•œ ๊ตญ์ง€ ์ปดํฌ๋„ŒํŠธ ํŠธ๋ฆฌ์—๋Š” ์ด˜์ด˜ํ•˜๊ฒŒ ์‰ด๋“œ ํŒŒ์ผ์„ ํด๋” ๋‹จ์œ„๋กœ ์‹ฌ์–ด๋†”์•ผ, ์‚ฌ์ดํŠธ ์ „์ฒด๊ฐ€ ์ค‘๋‹จ๋˜๋Š” ์˜ค๋ฅ˜ ํ™”๋ฉด(YOD: Yellow Screen of Death)์„ ๋ฉดํ•  ์ˆ˜ ์žˆ๋‹ค!


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

Q1. error.tsx๊ฐ€ ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ์—ฌ์•ผ ํ•˜๋Š” ์ด์œ ๋Š”?

โœ… ์ •๋‹ต: reset ๊ฐ™์€ ๋ณต๊ตฌ ์ธํ„ฐ๋ž™์…˜์„ ์ œ๊ณตํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค: ์—๋Ÿฌ ํ™”๋ฉด์€ ๋‹จ์ˆœ ์•ˆ๋‚ด๋ฌธ์ด ์•„๋‹ˆ๋ผ ์‚ฌ์šฉ์ž๊ฐ€ ๋‹ค์‹œ ์‹œ๋„ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒฝ๊ณ„๋‹ค. ๊ทธ๋ž˜์„œ error.tsx๋Š” use client๊ฐ€ ํ•„์š”ํ•œ ํŠน๋ณ„ ํŒŒ์ผ์ด๋‹ค.


Q2. not-found.tsx์™€ error.tsx๋ฅผ ๊ตฌ๋ถ„ํ•ด์•ผ ํ•˜๋Š” ์ด์œ ๋Š”?

โœ… ์ •๋‹ต: ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋ฆฌ์†Œ์Šค์™€ ์˜ˆ์™ธ๋กœ ์‹คํŒจํ•œ ์ƒํ™ฉ์€ ์‚ฌ์šฉ์ž ์•ˆ๋‚ด์™€ ์ƒํƒœ ์ฝ”๋“œ๊ฐ€ ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค: ์‚ญ์ œ๋œ ๊ฒŒ์‹œ๊ธ€์€ 404 ๊ฒฝํ—˜์œผ๋กœ, DB ์žฅ์• ๋Š” ๋ณต๊ตฌ ๊ฐ€๋Šฅํ•œ ์—๋Ÿฌ ํ™”๋ฉด์œผ๋กœ ๋‹ค๋ฃจ๋Š” ํŽธ์ด ์ž์—ฐ์Šค๋Ÿฝ๋‹ค. ๋‘˜์„ ์„ž์œผ๋ฉด ์‚ฌ์šฉ์ž์™€ ๊ฒ€์ƒ‰ ์—”์ง„ ๋ชจ๋‘์—๊ฒŒ ๋ชจํ˜ธํ•ด์ง„๋‹ค.


Q3. ์˜์ฒ ์ด์˜ ํ…Œ์ŠคํŠธ ํƒ€์ž„: ๊ฒŒ์‹œ๊ธ€ ์ƒ์„ธ ์กฐํšŒ์—์„œ ๊ถŒํ•œ์€ ์žˆ์ง€๋งŒ ๊ธ€ ID๊ฐ€ ์—†๋‹ค. ์–ด๋–ค ํ๋ฆ„์ด ๋งž์„๊นŒ?

โœ… ์ •๋‹ต: notFound()๋ฅผ ํ˜ธ์ถœํ•ด ํ•ด๋‹น ์„ธ๊ทธ๋จผํŠธ์˜ not-found UI๋กœ ๋ณด๋‚ธ๋‹ค.

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค: ์˜ˆ์™ธ๋ฅผ ๋˜์ ธ error.tsx๋กœ ๋ณด๋‚ด๋ฉด "๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ๋‹ค"๋Š” ๋ฉ”์‹œ์ง€๊ฐ€ ๋œ๋‹ค. ์‹ค์ œ๋กœ๋Š” ๋ฆฌ์†Œ์Šค๊ฐ€ ์—†๋Š” ๊ฒƒ์ด๋ฏ€๋กœ 404 ๊ฒฝ๊ณ„๋ฅผ ์“ฐ๋Š” ๊ฒŒ ๋” ์ •ํ™•ํ•˜๋‹ค.

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

์˜ค๋Š˜์€ ์—๋Ÿฌ ์ฒ˜๋ฆฌ๋ฅผ catch ํ•œ ์ค„์ด ์•„๋‹ˆ๋ผ ์‚ฌ์šฉ์ž ์—ฌ์ •์˜ ์ผ๋ถ€๋กœ ๋ณด๊ฒŒ ๋๋‹ค.

๐Ÿ’ก "์—๋Ÿฌ ๊ฒฝ๊ณ„๋Š” ์‹คํŒจ๋ฅผ ์ˆจ๊ธฐ๋Š” ์žฅ์น˜๊ฐ€ ์•„๋‹ˆ๋ผ ํšŒ๋ณต ๊ฐ€๋Šฅํ•œ ๋ฒ”์œ„๋ฅผ ์ •ํ•˜๋Š” ์žฅ์น˜๋‹ค."

๋‹ค์Œ์—๋Š” ์‹คํŒจ ์ข…๋ฅ˜๋ฅผ ๋จผ์ € ์ด๋ฆ„ ๋ถ™์ด๊ณ , ๊ทธ์— ๋งž๋Š” ๊ฒฝ๊ณ„ ํŒŒ์ผ์„ ๊ณ ๋ฅด๊ฒ ๋‹ค.

๐Ÿ”— ๋” ์•Œ์•„๋ณด๊ธฐ


๐Ÿ”— ๋” ์•Œ์•„๋ณด๊ธฐ