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

๋ฌธ์ œ ์ƒํ™ฉ

Next.js๋ฅผ ์‚ฌ์šฉํ•ด ์บ˜๋ฆฐ๋” ํŽ˜์ด์ง€๋ฅผ ๊ฐœ๋ฐœํ•˜๋Š” ์ค‘,
Supabase ํ†ต์‹  ์ค‘ ๋ฐœ์ƒํ•˜๋Š” ์—๋Ÿฌ๋ฅผ ํŽ˜์ด์ง€ ๋ผ์šฐํ„ฐ ์„ธ๊ทธ๋จผํŠธ ๋‚ด์˜ error.tsx๋กœ ํ†ตํ•ฉํ•ด์„œ ์ฒ˜๋ฆฌํ•˜๋ ค ํ–ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์—๋Ÿฌ๋ฅผ ๋˜์งˆ ๋•Œ ๋‚ด๊ฐ€ ์ง€์ •ํ•œ ๋ฉ”์‹œ์ง€์™€ ๋Ÿฐํƒ€์ž„ ์ค‘ ๋ฐœ์ƒํ•˜๋Š” ์‹œ์Šคํ…œ ๋ฉ”์‹œ์ง€๊ฐ€ ์„ž์—ฌ ๋‚˜์˜ค๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ๋‹ค.

๋˜ํ•œ,

 

  • app/api/**/route.ts (๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ)
  • supabase ํ˜ธ์ถœ ํ•จ์ˆ˜(fetch)
  • ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ (page.tsx)
  • ํƒ ์Šคํƒ ์ฟผ๋ฆฌ(useQuery)

์ด๋ ‡๊ฒŒ ์—ฌ๋Ÿฌ ๋ ˆ์ด์–ด๋ฅผ ๊ฑฐ์น˜๋ฉด์„œ ์—๋Ÿฌ ํ๋ฆ„์„ ๊น”๋”ํ•˜๊ฒŒ ์žก๋Š” ๊ฒƒ๋„ ๊ณ ๋ฏผ์ด ํ•„์š”ํ–ˆ๋‹ค.

 

์ตœ์ข…์ ์ธ ์—๋Ÿฌ ํ๋ฆ„์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

  • ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ
์„ฑ๊ณตor์—๋Ÿฌ๋ฅผ ๊ฐ์ฒด ํ˜•ํƒœ๋กœ ๋ฐ›์•„์„œ return ๐Ÿ‘‰๐Ÿป ํŽ˜์ด์ง€์—์„œ ์—๋Ÿฌ ๊ฐ์ฒด๋ฅผ ์žก์•„์„œ throw ๐Ÿ‘‰๐Ÿป error.tsx ์žก์Œ

 

๋”๋ณด๊ธฐ
import ENV from '@/constants/env.constant';
import { NextResponse } from 'next/server';

// ๊ณตํœด์ผ ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ
export async function GET(req: Request) {
  const { searchParams } = new URL(req.url);
  const year = searchParams.get('year') || new Date().getFullYear().toString();

  try {
    const url = `${ENV.HOLIDAY_URL}/getRestDeInfo?ServiceKey=${ENV.HOLIDAY_KEY}&solYear=${year}&_type=json&numOfRows=100`;
    const res = await fetch(url);

    if (!res.ok) {
      throw new Error(`๊ณตํœด์ผ API ์‘๋‹ต ์˜ค๋ฅ˜: ${res.statusText}`);
    }

    const data = await res.json();
    // ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ ๊ฒ€์ฆ
    if (!data.response || !data.response.body) {
      console.error('์œ ํšจํ•˜์ง€ ์•Š์€ API ์‘๋‹ต ํ˜•์‹:', data);
      return NextResponse.json({ error: '๊ณตํœด์ผ ๋ฐ์ดํ„ฐ ํ˜•์‹์ด ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค' }, { status: 500 });
    }
    return NextResponse.json(data); //์ •์ƒ ์‘๋‹ต ๋ฐ˜ํ™˜
  } catch (error) {
    console.error('๊ณตํœด์ผ ๋ฐ์ดํ„ฐ ํ˜ธ์ถœ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ:', error);
    return NextResponse.json({ error: '๊ณตํœด์ผ์„ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค' }, { status: 500 });
  }
}
  • supabase ํ†ต์‹ 
๊ฐ ํ†ต์‹  ํ•จ์ˆ˜์—์„œ console.error & throw new Error ๐Ÿ‘‰๐Ÿป ํŽ˜์ด์ง€ ๋‚ด์˜ ์ฟผ๋ฆฌ ํ›…์—์„œ error ๊บผ๋‚ด๊ธฐ  ๐Ÿ‘‰๐Ÿป ๊ฐ ์—๋Ÿฌ ์ฒ˜๋ฆฌ ํ›„ throw ๐Ÿ‘‰๐Ÿป error.tsx ์žก์Œ
๋”๋ณด๊ธฐ
1. plans ๋ฐ์ดํ„ฐ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ
export const getPlans = async (planId: string): Promise<PlansType> => {
  const { data, error } = await supabase.from(PLANS).select('*').eq('plan_id', planId).single();

  if (error) {
    console.error('์•ฝ์†์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค:', error);
    throw new Error('์•ฝ์†์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.');
  }

  return data;
};

2. ์ฟผ๋ฆฌ ํ›…์—์„œ error ๊บผ๋‚ด๊ธฐ
  const { data: holidays, error: holidaysError } = useGetHolidays(String(calendarYear)); //๊ณตํœด์ผ
  const { data: events, isPending, error: plansError } = useGetCalendarPlans(user, calendarYear, moment); //์•ฝ์†(readonly)
  const {
    data: selectedPlanData,
    error: selectPlanError,
    refetch: refetchSelectedPlan,
  } = useGetSelectPlan(selectedPlanId ?? '');
    const { mutate: updateEvent, error: updatePlanError } = useUpadateEventMutate();
    
    
3. ์—๋Ÿฌ ๋ชจ์•„์„œ throw
  if (holidaysError || plansError || selectPlanError || updatePlanError) {
    const error = new Error('์บ˜๋ฆฐ๋” ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.') as Error & {
      originalError?: Error | null;
    };
    error.originalError = holidaysError ?? plansError ?? selectPlanError ?? updatePlanError;
    throw error;
  }

์—๋Ÿฌ ํ๋ฆ„ ์ „์ฒด ๊ตฌ์กฐ

์—๋Ÿฌ ์ฒ˜๋ฆฌ๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ทœ์น™์œผ๋กœ ์ •๋ฆฌํ–ˆ๋‹ค.

 

1. ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ(app/api//route.ts)**

 

์„œ๋ฒ„ ์‚ฌ์ด๋“œ์—์„œ ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ → NextResponse.json({ error }, { status }) ๋กœ ํด๋ผ์ด์–ธํŠธ์— ์—๋Ÿฌ ์ „๋‹ฌ

return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });

 

 

 

2. ํ†ต์‹  ๋กœ์ง (Supabase ํ˜ธ์ถœ ํ•จ์ˆ˜ ๋“ฑ)

 

fetch ํ•จ์ˆ˜์—์„œ๋Š” ํ†ต์‹  ์‹คํŒจ ์‹œ throw new Error('์ปค์Šคํ…€ ๋ฉ”์‹œ์ง€') ๋กœ ์—๋Ÿฌ ๋˜์ง€๊ธฐ

๐Ÿ”ฅ ์ด๋•Œ ํ†ต์‹  ์ž์ฒด๊ฐ€ ์•ˆ ๋œ๋‹ค๋ฉด ๋Ÿฐํƒ€์ž„ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์œผ๋‹ˆ ์ฃผ์˜.

if (error) {
  throw new Error('์•ฝ์†์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.');
}

 

 

 

3. ํŽ˜์ด์ง€ ์ปดํฌ๋„ŒํŠธ (ex. app/(routes)/calendar/page.tsx)

 

์ง์ ‘์ ์ธ ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ throw new Error('๋ฉ”์‹œ์ง€')  error.tsx๋กœ ์—ฐ๊ฒฐ๋˜๋„๋ก ์„ค์ •

if (!data) {
  throw new Error('๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.');
}

 

 

 

4. ํƒ ์Šคํƒ ์ฟผ๋ฆฌ (useQuery, useMutation)

 

์ฟผ๋ฆฌ ํ›… ๋‚ด๋ถ€์—์„œ๋Š” Next.js์˜ error.tsx๋ฅผ ์ง์ ‘ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š๊ณ 
error, isError ํ”Œ๋ž˜๊ทธ๋ฅผ ํ†ตํ•ด ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ ์•ˆ์—์„œ ์—๋Ÿฌ ์ฒ˜๋ฆฌ

const { data, error, isError } = useGetCalendarPlans();

if (isError) {
  throw error;
}

 

 

 

โœ… ํ๋ฆ„ ์ •๋ฆฌ

 

  • ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์—๋Ÿฌ๋Š” json ์‘๋‹ต์œผ๋กœ ๋‚ด๋ ค์˜ด
  • ํ†ต์‹ (fetcher) ๋‹จ์—์„œ ๋ฐ›์€ ์—๋Ÿฌ๋Š” throw new Error๋กœ ๋ช…์‹œ์ ์œผ๋กœ ๋˜์ง
  • ํŽ˜์ด์ง€ ์ปดํฌ๋„ŒํŠธ์—์„œ๋Š” ํ†ตํ•ฉ์ ์œผ๋กœ ์—๋Ÿฌ๋ฅผ ๋ฐ›์•„ ํ•„์š”ํ•œ ๊ฒฝ์šฐ ๋‹ค์‹œ throw
  • ์ตœ์ข…์ ์œผ๋กœ error.tsx๊ฐ€ ๋ชจ๋“  ์—๋Ÿฌ๋ฅผ ๋ฐ›์•„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ตฌ์กฐ

 

 

 

error.tsx

error.tsx์—์„œ๋Š” ์ด๋ ‡๊ฒŒ ๋ฉ”์ธ ์—๋Ÿฌ์™€ ์„œ๋ธŒ ์—๋Ÿฌ(originalError)๋ฅผ ๋‚˜๋ˆ„์–ด ์ถœ๋ ฅํ–ˆ๋‹ค.

  • ๋ฉ”์ธ ์—๋Ÿฌ: ์บ˜๋ฆฐ๋” ํŽ˜์ด์ง€์—์„œ throwํ•œ ๋ฉ”์„ธ์ง€ '์บ˜๋ฆฐ๋” ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.'
  • ์„œ๋ธŒ ์—๋Ÿฌ: supabase ํ†ต์‹  ๋กœ์ง์—์„œ throwํ•œ ๋””ํ…Œ์ผ ๋ฉ”์„ธ์ง€ '์•ฝ์†์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”'

์—๋Ÿฌ๋ฅผ ์ฝ˜์†”์—๋„ ์ถœ๋ ฅํ•ด์„œ ๋””๋ฒ„๊น…ํ•˜๊ธฐ ์‰ฝ๊ฒŒ ์„ค์ •ํ–ˆ๋‹ค.

'use client';

import GenericError from '@/components/Error';

export default function ErrorPage({ error, reset }: { error: Error & { originalError?: Error }; reset: () => void }) {
  console.error('๋ฉ”์ธ ์—๋Ÿฌ:', error);

  if (error.originalError) {
    console.error('์›๋ž˜ ์—๋Ÿฌ:', error.originalError);
  }

  return (
    <GenericError
      error={error}
      originalError={error.originalError ?? null}
      refetch={reset} // reset ํ•จ์ˆ˜๋Š” Next.js๊ฐ€ ์ œ๊ณตํ•˜๋Š” ์žฌ์‹œ๋„ ํ•จ์ˆ˜
    />
  );
}

 

 

 

GenericError ์ปดํฌ๋„ŒํŠธ

๋ Œ๋”๋ง ์‹œ, error.message์™€ originalError.message ๋ฅผ ๊ฐ๊ฐ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ๋„๋ก ํ–ˆ๋‹ค.

import { Warning } from '@phosphor-icons/react';

type GenericErrorProps = {
  error?: Error | null;
  originalError: Error | null;
  refetch?: () => void;
};

export default function GenericError({ error, refetch, originalError }: GenericErrorProps) {
  return (
    <div className='flex min-h-screen flex-col items-center justify-center gap-8'>
      <Warning size={60} className='text-secondary-600' />
      <h2 className='title text-[45px] font-bold text-primary-700'>Error!</h2>
      {error?.message && originalError?.message && (
        <section className='flex flex-col items-center text-[23px] text-primary-500'>
          <p>{error?.message}</p>
          <p>Detail: {originalError?.message}</p>
        </section>
      )}
      {refetch && (
        <button
          onClick={refetch}
          className='rounded-md border-2 bg-primary-500 p-4 font-bold text-white hover:bg-primary-600'
        >
          ๋‹ค์‹œ ์‹œ๋„
        </button>
      )}
    </div>
  );
}

 

 

์ผ๋ถ€๋Ÿฌ ํ†ต์‹  ์—๋Ÿฌ๋ฅผ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•

 

Supabase ์š”์ฒญ ์ค‘ ๋ฐœ์ƒํ•˜๋Š” ์—๋Ÿฌ๋Š” ์ž˜ ์žกํžˆ์ง€๋งŒ,
์ฝ”๋“œ ์ž์ฒด์—์„œ ๋Ÿฐํƒ€์ž„ ์—๋Ÿฌ๊ฐ€ ๋‚˜๋ฉด Supabase์— ๋„๋‹ฌ์กฐ์ฐจ ๋ชป ํ•˜๊ณ  ์ฃฝ๋Š”๋‹ค.

์˜ˆ๋ฅผ ๋“ค๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด Supabase ์š”์ฒญ์„ ๋ณด๋‚ด๊ธฐ๋„ ์ „์— ๋Ÿฐํƒ€์ž„ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.
(๋”ฐ๋ผ์„œ originalError๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š๋Š”๋‹ค.)

const { error } = await supabase.from(PLAS).select('*'); // PLAS๊ฐ€ undefined

 

 

๋”ฐ๋ผ์„œ Supabase ํ†ต์‹  ์‹คํŒจ๋ฅผ ์ผ๋ถ€๋Ÿฌ ๋งŒ๋“ค๊ณ  ์‹ถ์œผ๋ฉด

  • ์ž˜๋ชป๋œ ํ…Œ์ด๋ธ”๋ช… ์‚ฌ์šฉ
  • ํ•„์ˆ˜ ์ปฌ๋Ÿผ์„ ๋ˆ„๋ฝ
  • ์กด์žฌํ•˜์ง€ ์•Š๋Š” ํ•„๋“œ๋ฅผ ์ฟผ๋ฆฌ

๋“ฑ์„ ํ†ตํ•ด ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๊ณ ,
์ •์ƒ์ ์œผ๋กœ ์‘๋‹ต์ด ์˜ค๊ฒŒ ํ•œ ๋’ค ๊ฑฐ๊ธฐ์„œ error ์ฒดํฌ ํ›„ ๋˜์ง€๋Š” ์‹์œผ๋กœ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•œ๋‹ค.

 

 

์ตœ์ข… ๊ฒฐ๊ณผ ๋ฐ ๋А๋‚€์ 

 

์ด๋ฒˆ ์ž‘์—…์„ ํ†ตํ•ด ๋Ÿฐํƒ€์ž„ ์—๋Ÿฌ์™€ ํ†ต์‹  ์—๋Ÿฌ๋ฅผ ๋ช…ํ™•ํžˆ ๊ตฌ๋ถ„ํ•ด์„œ ๋‹ค๋ค„์•ผ ํ•œ๋‹ค๋Š” ๊ฑธ ๋‹ค์‹œ ๊นจ๋‹ฌ์•˜๋‹ค.

๋˜ํ•œ throw new Error๋ฅผ ์“ฐ๋Š” ์œ„์น˜, ์—๋Ÿฌ๋ฅผ ๋ฐ›๋Š” ์œ„์น˜(error.tsx), ํ‘œ์‹œํ•˜๋Š” ๋ฐฉ๋ฒ•(GenericError ์ปดํฌ๋„ŒํŠธ)๊นŒ์ง€ ๋ชจ๋‘ ์„ค๊ณ„ํ•ด์•ผ
์—๋Ÿฌ ํ•ธ๋“ค๋ง์ด ์™„์„ฑ๋œ๋‹ค๋Š” ๊ฒƒ์„ ๊ฒฝํ—˜ํ–ˆ๋‹ค.

๊ณต์ง€์‚ฌํ•ญ
์ตœ๊ทผ์— ์˜ฌ๋ผ์˜จ ๊ธ€
์ตœ๊ทผ์— ๋‹ฌ๋ฆฐ ๋Œ“๊ธ€
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
๊ธ€ ๋ณด๊ด€ํ•จ