ํ‹ฐ์Šคํ† ๋ฆฌ ๋ทฐ

Language/Next

[/\/] Error Handling

์ฑ„._. 2025. 3. 19. 11:34

Next.js๋ฅผ ํ™œ์šฉํ•œ ๊ฐœ์ธ ๊ณผ์ œ๋ฅผ Vercel์—์„œ ๋ฐฐํฌํ•ด๋ณด์•˜๋‹ค.

๋ถ„๋ช… ๊ฐœ๋ฐœ ๋ชจ๋“œ์—์„œ๋Š” ์•„๋ฌด ๋ฌธ์ œ ์—†์ด ์ž‘๋™ํ•˜๋˜ ํ”„๋กœ์ ํŠธ๊ฐ€ ๋นŒ๋“œ ๊ณผ์ •์—์„œ ๋งŽ์€ ์˜ค๋ฅ˜๋ฅผ ๋ฑ‰์–ด๋ƒˆ๋‹ค.

ํ•˜๋‹จ์œผ๋กœ๋„ ๊ณ„์† ์ด์–ด์ง€๋Š” ์ˆ˜๋งŽ์€ ์˜ค๋ฅ˜..

 

์—ฌ๋Ÿฌ ์›์ธ์ด ์žˆ์—ˆ๊ณ  ํŒŒ์ผ ๋ช…์ด ๋‚˜์™€์žˆ์ง€๋งŒ, ์—๋Ÿฌ์˜ ์ตœ์ข… ํŽ˜์ด์ง€(?)๋งŒ ๋‚˜ํƒ€๋‚˜์žˆ์—ˆ๊ธฐ ๋•Œ๋ฌธ์—

์ •ํ™•ํžˆ ์–ด๋А ๊ณผ์ •์—์„œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฑด์ง€ ํŒŒ์•…ํ•˜๊ธฐ ์–ด๋ ค์› ๋‹ค.

 

 

ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ ์—๋Ÿฌ ์ฒ˜๋ฆฌ

 

ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ๋Š” useQuery๋ฅผ ํ†ตํ•ด ์—๋Ÿฌ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๊ณ  ์žˆ์—ˆ๋‹ค.

๋˜ํ•œ ํด๋ผ์ด์–ธํŠธ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ผ์šฐํ„ฐ ํ•ธ๋“ค๋Ÿฌ์—์„œ๋„ ์—๋Ÿฌ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๊ณ  ์žˆ์—ˆ๋‹ค.

๋”๋ณด๊ธฐ
// rotation > page.tsx

...
export default function Rotationpage() {
  const { data: imgUrl, isPending, isError, error } = getImgUrl();
  const { data: rotation = [] } = getRotation();

  if (isPending) {
    return <Loading />;
  }

  if (isError) {
    return (
      <>
        <div>์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค!</div>
        <p>์—๋Ÿฌ:{error.message}</p>
      </>
    );
  }
...

 

// api > rotation > route.ts

// ๋กœํ…Œ์ด์…˜ API ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ + ํ•„ํ„ฐ
export const GET = async (): Promise<NextResponse> => {
  try {
    const res = await fetch(ROTATION_API_URL, {
      headers: {
        "X-Riot-Token": process.env.RIOT_API_KEY!,
      },
    });

    // ์‚ฌ์šฉ์ž ์š”์ฒญ ์—๋Ÿฌ
    if (!res) {
      return NextResponse.json(
        { message: "error! ๋กœํ…Œ์ด์…˜ ์ฑ”ํ”ผ์–ธ ์ •๋ณด๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค!" },
        { status: 400 },
      );
    }

    // ํ•„ํ„ฐ
    const rotationData: ChampionRotation = await res.json(); // freeChampionIds ๋ฐฐ์—ด ํฌํ•จ ๊ฐ์ฒด
    const championData: Champions[] = await fetchChampionList(); // ๋ชจ๋“  ์ฑ”ํ”ผ์–ธ ๋ฆฌ์ŠคํŠธ ๋ฐฐ์—ด
    const filterData: Champions[] = championData.filter((el) => {
      return rotationData.freeChampionIds.includes(parseInt(el.key));
    }); // ์ฑ”ํ”ผ์–ธ ํ‚ค๋ฅผ ํฌํ•จํ•œ ๋กœํ…Œ์ด์…˜ ์•„์ด๋””๋ฅผ ์ฑ”ํ”ผ์–ธ ๋ฆฌ์ŠคํŠธ์—์„œ filter
    return NextResponse.json(filterData, { status: 200 });
  } catch (error) {
    console.log("๋กœํ…Œ์ด์…˜ ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ ์—๋Ÿฌ ๋ฐœ์ƒ", error);
    // ์„œ๋ฒ„ ์—๋Ÿฌ
    return NextResponse.json(
      { message: "error! ๋กœํ…Œ์ด์…˜ ํŽ˜์ด์ง€๋ฅผ ์ฝ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค!" },
      { status: 500 },
    );
  }
};

 

 

์„œ๋ฒ„ ์‚ฌ์ด๋“œ ์—๋Ÿฌ ์ฒ˜๋ฆฌ

 

๊ทธ๋Ÿผ ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ๋Š” ์—๋Ÿฌ ์ฒ˜๋ฆฌ๋ฅผ ์–ด๋””์„œ, ์–ด๋–ป๊ฒŒ ํ•ด์•ผํ• ๊นŒ?

์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ ๋‚ด์—์„œ ์ง์ ‘ API ๋ฐ์ดํ„ฐ๋ฅผ ํŽ˜์นญํ•˜๊ณ  ์ฒ˜๋ฆฌํ•˜๋Š” ์„œ๋ฒ„ ์•ก์…˜๊ณผ ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ์—์„œ ์—๋Ÿฌ ์ฒ˜๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 

๐Ÿ“์‹œ๋„ 1. ์„œ๋ฒ„ ์•ก์…˜์—์„œ try...catch ๋ฌธ์œผ๋กœ ์—๋Ÿฌ ์ฒ˜๋ฆฌ

๋‹ค์Œ๊ณผ ๊ฐ™์€ ์„œ๋ฒ„ ์•ก์…˜์—์„œ try catch๋ฅผ ํ™œ์šฉํ•ด๋ณด์•˜๋‹ค.
๊ทธ๋Ÿฌ๋‚˜ 'Champion[]์˜ ํƒ€์ž…์— error๊ฐ€ ์—†๋‹ค'๋Š” ์˜ค๋ฅ˜๊ฐ€ ๋‚ฌ๋‹ค.

Promise<Campions[]>๋กœ ์„ค์ •ํ•ด๋‘” ์ œ๋„ค๋ฆญ ํƒ€์ž…์— ๋ฐ˜ํ•ด catch๋ฌธ์˜ return์˜ ๊ฐ’์ด 'error' ํƒ€์ž…์ด๋ผ ์˜ค๋ฅ˜๊ฐ€ ๋‚ฌ๋‹ค.

// utils > serverApi.ts

// ์ฑ”ํ”ผ์–ธ ๋ชฉ๋ก ํŒจ์นญ
export async function fetchChampionList(): Promise<Champions[]> {
  try {
    const chamUrl = await CHAMPION_LIST_URL();
    const res = await fetch(chamUrl, { next: { revalidate: 86400 } });
    const { data } = await res.json();

    return Object.values(data);
  } catch (error) {
    console.log("์ฑ”ํ”ผ์–ธ ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ ์‹คํŒจ", error);
    return { error: "์ฑ”ํ”ผ์–ธ ๋ชฉ๋ก ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค!" };
  }
}

 

๋”ฐ๋ผ์„œ ์„œ๋ฒ„ ์•ก์…˜์—์„œ๋Š” ์—๋Ÿฌ๋ฅผ throwํ•˜๊ณ , ํ•ด๋‹น ์„œ๋ฒ„ ์•ก์…˜์„ ์‚ฌ์šฉํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ์—์„œ ์—๋Ÿฌ๋ฅผ ๋ฐ›์•„์•ผ ํ–ˆ๋‹ค.

 

 

๐Ÿ“์‹œ๋„ 2. ์„œ๋ฒ„ ์•ก์…˜์—์„œ ์—๋Ÿฌ throw

throw new Error๋ฅผ ํ†ตํ•ด ์—๋Ÿฌ๋ฅผ ๋˜์กŒ๋‹ค.

// ์ฑ”ํ”ผ์–ธ ๋ชฉ๋ก ํŒจ์นญ
export async function fetchChampionList(): Promise<Champions[]> {
  const chamUrl = await CHAMPION_LIST_URL();
  const res = await fetch(chamUrl, { next: { revalidate: 86400 } });
  const { data } = await res.json();

  //ํ•ด๋‹น ํŽ˜์ด์ง€๋กœ ์—๋Ÿฌ ๋ณด๋‚ด๊ธฐ
  if (!res.ok) {
    throw new Error("์ฑ”ํ”ผ์–ธ ๋ชฉ๋ก ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค!");
  }
  return Object.values(data);
}

 

 

๐Ÿค”์—๋Ÿฌ๋ฅผ throw ํ•  ๋•Œ 'new Error'๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ 

๋”๋ณด๊ธฐ
  • new Error("๋ฉ”์„ธ์ง€")๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋” ๋งŽ์€ ์ •๋ณด๋ฅผ ํฌํ•จํ•œ ํ‘œ์ค€ ์—๋Ÿฌ ๊ฐ์ฒด ์ƒ์„ฑ

๐Ÿ“throw "๋ฌธ์ž์—ด"

  • ์‚ฌ์šฉ : throw "์—๋Ÿฌ ๋ฐœ์ƒ!";
  • ์‹คํ–‰ ๊ฒฐ๊ณผ : Uncaught (in promise) ์—๋Ÿฌ ๋ฐœ์ƒ!
  • ๋ฌธ์ œ์  
    • throwํ•œ ๊ฐ’์ด ๋‹จ์ˆœํ•œ ๋ฌธ์ž์—ด์ด๋ผ ์—๋Ÿฌ ๊ฐ์ฒด๊ฐ€ ์•„๋‹˜
    • ์Šคํƒ ํŠธ๋ ˆ์ด์Šค(stack trace)๊ฐ€ ์ œ๊ณต๋˜์ง€ ์•Š์Œ → ๋””๋ฒ„๊น… ์–ด๋ ค์›€
    • catch์—์„œ error.message์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Œ (error ์ž์ฒด๊ฐ€ ๋ฌธ์ž์—ด์ด๊ธฐ ๋•Œ๋ฌธ)

 

๐Ÿ“throw new Error("์—๋Ÿฌ ๋ฐœ์ƒ")

  • ์‚ฌ์šฉ : throw new Error("์—๋Ÿฌ ๋ฐœ์ƒ!");
  • ์‹คํ–‰ ๊ฒฐ๊ณผ
    • Uncaught (in promise) Error: ์—๋Ÿฌ ๋ฐœ์ƒ!
          at fetchChampionList (actions.ts:5)
          at async handleClick (component.tsx:10)
  • ์žฅ์ 
    • Error ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ํ‘œ์ค€์ ์ธ ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ
    • ์Šคํƒ ํŠธ๋ ˆ์ด์Šค(stack trace) ์ œ๊ณต → ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ ์œ„์น˜๋ฅผ ์‰ฝ๊ฒŒ ์ฐพ์„ ์ˆ˜ ์žˆ์Œ
    • catch(error)์—์„œ error.message๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฉ”์‹œ์ง€๋งŒ ์ถ”์ถœ ๊ฐ€๋Šฅ

 

 

์—๋Ÿฌ ํ•ธ๋“ค๋ง (error.tsx)

์ด์ œ throwํ•œ ์—๋Ÿฌ๋ฅผ ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ฐ›์œผ๋ฉด ๋˜๋Š”๋ฐ, ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ๋งˆ๋‹ค try catch๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์—๋Ÿฌ๋ฅผ ๋ฐ›์•„์•ผํ–ˆ๋‹ค..

๊ทธ๋•Œ ๋ฐœ๊ฒฌํ•œ ๊ฒƒ์ด '์—๋Ÿฌ ํ•ธ๋“ค๋ง' ์ด์—ˆ๋‹ค.

 

๋„ฅ์ŠคํŠธ ๊ณต์‹๋ฌธ์„œ์—๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋‚˜์™€์žˆ๋‹ค.

์—๋Ÿฌ๋Š” ์˜ˆ์ƒ๋œ ์—๋Ÿฌ์™€ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์˜ˆ์™ธ ๋‘ ๊ฐ€์ง€ ๋ฒ”์ฃผ๋กœ ๋‚˜๋ˆŒ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

- ์˜ˆ์ƒ๋œ ์—๋Ÿฌ๋ฅผ ๋ฐ˜ํ™˜ ๊ฐ’์œผ๋กœ ๋ชจ๋ธ๋ง: ์„œ๋ฒ„ ์•ก์…˜์—์„œ ์˜ˆ์ƒ๋œ ์—๋Ÿฌ๋ฅผ try/catch๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์„ ํ”ผํ•˜์‹ญ์‹œ์˜ค.
  useActionState๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด๋Ÿฌํ•œ ์—๋Ÿฌ๋ฅผ ๊ด€๋ฆฌํ•˜๊ณ  ํด๋ผ์ด์–ธํŠธ์— ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

- ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์—๋Ÿฌ๋Š” ์—๋Ÿฌ ๊ฒฝ๊ณ„๋กœ ์ฒ˜๋ฆฌ: error.tsx ๋ฐ global-error.tsx ํŒŒ์ผ์„ ์‚ฌ์šฉํ•˜์—ฌ ์—๋Ÿฌ ๊ฒฝ๊ณ„๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ  ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์—๋Ÿฌ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋ฉฐ ๋Œ€์ฒด UI๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

 

try catch๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์˜ˆ์ƒ๋œ ์—๋Ÿฌ(thorwํ•œ ์—๋Ÿฌ)๋งŒ์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ํ•œ๋‹ค.

error.tsx ๋ฐ global-error.tsx ํŒŒ์ผ์„ ํ†ตํ•ด ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์—๋Ÿฌ๊นŒ์ง€ ํ•ธ๋“ค๋ง์„ ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 

error.tsx๋Š” ์„ธ๊ทธ๋จผํŠธ ๋‚ด์— ๋‹จ ํ•œ ๊ฐœ์˜ ํŒŒ์ผ์„ ๋‘์–ด ํ•ด๋‹น ์„ธ๊ทธ๋จผํŠธ ๋‚ด๋ถ€ ํŽ˜์ด์ง€์˜ ์—๋Ÿฌ ์ฒ˜๋ฆฌ๋ฅผ ๋‹ด๋‹นํ•œ๋‹ค.

ํŒŒ์ผ๋ช…์„ ์ •ํ™•ํžˆ ๊ธฐ์žฌํ•ด์•ผ Next์—์„œ ์ž๋™์œผ๋กœ ์ธ์‹ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 

๐Ÿค”ํŽ˜์ด์ง€์—์„œ ์ง์ ‘ ์—๋Ÿฌ๋ฅผ ๋ฐ›์ง€ ์•Š๊ณ  error.tsx์—์„œ ์ผ๊ด„์ ์œผ๋กœ ์ฒ˜๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•œ๊ฐ€?

๊ทธ๋ ‡๋‹ค!
error.tsx์—์„œ๋งŒ ์—๋Ÿฌ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ตฌ์กฐ๋กœ, ํŽ˜์ด์ง€์—์„œ๋Š” ์—๋Ÿฌ๋ฅผ ์ง์ ‘ ์ฒ˜๋ฆฌํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.

ํŽ˜์ด์ง€์—์„œ ๋ฐ์ดํ„ฐ ์š”์ฒญ์ด ์‹คํŒจํ•˜๋ฉด, error.tsx ํŒŒ์ผ์ด ๊ทธ ์—๋Ÿฌ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ณ , ์‚ฌ์šฉ์ž์—๊ฒŒ ์ ์ ˆํ•œ UI๋ฅผ ๋ณด์—ฌ์ฃผ๊ฒŒ ๋œ๋‹ค.

 

๐Ÿค”ํŒŒ์ผ ์œ„์น˜์— ๋”ฐ๋ฅธ ์ฐจ์ด๋Š”? (ํ•ด๋‹น ํŽ˜์ด์ง€์˜ ์„ธ๊ทธ๋จผํŠธ ๋‚ด๋ถ€์™€ app ํ•˜์œ„ )

์„ธ๊ทธ๋จผํŠธ ๋‚ด๋ถ€์— ์œ„์น˜ํ•œ ๊ฒฝ์šฐ ํ•ด๋‹น ํŽ˜์ด์ง€์—๋งŒ ์ ์šฉํ•˜๋Š” ์—๋Ÿฌ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

app์— ์œ„์น˜ํ•œ ๊ฒฝ์šฐ ํ•˜์œ„์˜ ๋ชจ๋“  ํŽ˜์ด์ง€์— ๋™์ผํ•œ ์—๋Ÿฌ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ์ฝ”๋“œ ์ค‘๋ณต์„ ๋ฐฉ์ง€ํ•œ๋‹ค.

 

๋”ฐ๋ผ์„œ ๋‚˜๋Š” app ๊ฒฝ๋กœ์— error.tsx ํŒŒ์ผ์„ ๋‘์–ด ๋ชจ๋“  ํŽ˜์ด์ง€์— ๋™์ผํ•œ ์—๋Ÿฌ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ œ์‹œํ•˜๋„๋ก ํ•˜์˜€๋‹ค.

๋”๋ณด๊ธฐ
// rotation > page.tsx

"use client";

import { getImgUrl, getRotation } from "../hooks/quries";
import Card from "@/_components/Card";
import Loading from "../loading";

export default function Rotationpage() {
  const { data: imgUrl, isPending } = getImgUrl();
  const { data: rotation = [] } = getRotation();

  if (isPending) {
    return <Loading />;
  }

  return (
    <main className="m-[50px]">
      <h1 className="flexCenter title mt-[80px] text-[30px]">
        Champion Rotation (This week for free!)
      </h1>
      <div className="itemGrid mt-[30px] auto-rows-[minmax(200px,auto)]">
        {rotation.map((champion) => (
          <Card
            key={champion.key}
            id={champion.id}
            name={champion.name}
            title={champion.title}
            image={champion.image}
            img_Url={imgUrl ?? ""} // ์—๋Ÿฌ ์ฒ˜๋ฆฌ ์ƒ๋žต ์‹œ, data๋Š” undefined๊ฐ€ ๋  ๊ฐ€๋Šฅ์„ฑ
          />
        ))}
      </div>
    </main>
  );
}

 

useQuery ๋กœ ์—๋Ÿฌ ์ƒํƒœ ๊ด€๋ฆฌํ•˜๊ธฐ

๋˜ํ•œ, error.tsx๋Š” (๊ฑฐ์˜ ๋ชจ๋“  ๊ฒฝ์šฐ) ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ์—ฌ์•ผ ํ•œ๋‹ค.

error.tsx๋Š” '๋‹ค์‹œ ์‹œ๋„' ๋ฒ„ํŠผ๊ณผ ๊ฐ™์ด ์‚ฌ์šฉ์ž์™€์˜ ์ƒํ˜ธ์ž‘์šฉ์ด ํ•„์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ์—ฌ์•ผ ํ•œ๋‹ค๋Š” ์  !!

๋”ฐ๋ผ์„œ useQuery๋กœ๋„ ์—๋Ÿฌ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

// app > error.tsx

"use client";

import { getChampionError } from "./hooks/quries";
import Loading from "./loading";

export default function Error() {
  const { error, isPending, isError, refetch } = getChampionError();

  if (isPending) {
    return <Loading />;
  }

  if (isError) {
    return (
      <div className="flexCenter mt-[300px] flex-col gap-8">
        <h1 className="title text-[50px]">์—๋Ÿฌ ๋ฐœ์ƒ!</h1>
        <p className="text-white">์—๋Ÿฌ ๋ฉ”์„ธ์ง€ : {error.message}</p>
        <button
          onClick={() => refetch()}
          className="rounded-md border-2 border-emerald-100 p-4 text-white"
        >
          ๋‹ค์‹œ ์‹œ๋„
        </button>
      </div>
    );
  }
}

 

 

๊ฒฐ๊ณผ์ ์œผ๋กœ ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ์—์„œ useQuery๋กœ ๊ด€๋ฆฌํ•˜๋˜ ์—๋Ÿฌ ์ƒํƒœ๋ฅผ error.tsx์—์„œ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ๋‹ค.

app ๊ฒฝ๋กœ์— error.tsx๋ฅผ ๋‘์–ด ํด๋ผ์ด์–ธํŠธ/์„œ๋ฒ„ ์—๋Ÿฌ๋ฅผ ์ผ๊ด„์ ์œผ๋กœ ์ฒ˜๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•ด์กŒ๋‹ค.

 

๋งˆ์ฐฌ๊ฐ€์ง€๋กœ loading.tsx ํŒŒ์ผ๋„ ๋™์ผํ•œ ๋ฐฉ์‹์œผ๋กœ app์— ๋‘์–ด ๋ชจ๋“  ํŽ˜์ด์ง€์— ์ ์šฉํ•˜์˜€๋‹ค.

// app > loading.tsx

export default function Loading() {
  return (
    <div className="m-auto my-5 flex h-[200px] w-[200px] items-center justify-center">
      <div className="h-12 w-12 animate-spin rounded-full border-4 border-white/30 border-t-white"></div>
    </div>
  );
}

 

 

๊ณต์ง€์‚ฌํ•ญ
์ตœ๊ทผ์— ์˜ฌ๋ผ์˜จ ๊ธ€
์ตœ๊ทผ์— ๋‹ฌ๋ฆฐ ๋Œ“๊ธ€
Total
Today
Yesterday
๋งํฌ
TAG
more
ยซ   2026/03   ยป
์ผ ์›” ํ™” ์ˆ˜ ๋ชฉ ๊ธˆ ํ† 
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
๊ธ€ ๋ณด๊ด€ํ•จ