๐Ÿ”ฅ Trouble Shooting - Next.js ์„œ๋ฒ„/ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ API ํ˜ธ์ถœ ์˜ค๋ฅ˜

์Š˜ยท2025๋…„ 3์›” 24์ผ

๐Ÿ”ฅ Trouble Shooting

๋ชฉ๋ก ๋ณด๊ธฐ
14/23

๐Ÿšจ ๋ฌธ์ œ ์ƒํ™ฉ

์ƒ์„ธํŽ˜์ด์ง€์—์„œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ณผ์ •์—์„œ ๋ผ์šฐํ„ฐ ํ•ธ๋“ค๋Ÿฌ๋กœ ๋งŒ๋“  api๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ด์„œ api๋ฅผ ํ˜ธ์ถœํ•˜์˜€๋Š”๋ฐ, Next.js ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ(page.tsx, generateMetadata)์—์„œ API ํ˜ธ์ถœ ์‹œ "Failed to parse URL" ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ์—์„œ๋Š” ์ž˜ ์ž‘๋™ํ•˜๋˜ API ํ˜ธ์ถœ ํ•จ์ˆ˜๊ฐ€ ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ์—์„œ๋Š” ๋™์ž‘ํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

๐Ÿ’ป ๋ฌธ์ œ ๋ฐœ์ƒ ์ฝ”๋“œ

// utils/api/hospitalDetail.ts
const hospitalDetail = async (id: string) => {
  try {
    const res = await fetch(`/api/hospitalDetail?id=${id}`);
    
    if (!res.ok) {
      throw new Error('Failed to fetch hospital detail');
    }
    const data = await res.json();
    return data;
  } catch (error) {
    console.error('Error fetching hospital details:', error);
    throw error;
  }
};

// app/hospital/[...id]/page.tsx
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
  const id = params?.id[0];
  
  try {
    // ์—ฌ๊ธฐ์„œ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: Failed to parse URL
    const detailData = await hospitalDetail(id);
    return {
      title: detailData.dutyName,
      description: detailData.dutyAddr,
    };
  } catch (error) {
    console.error('Error:', error);
    return { title: '๋ณ‘์› ์ •๋ณด' };
  }
}

๐Ÿค” ์›์ธ ๋ถ„์„

์ด ๋ฌธ์ œ๋Š” Next.js์˜ ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ์™€ ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ์˜ ์‹คํ–‰ ํ™˜๊ฒฝ ์ฐจ์ด ๋•Œ๋ฌธ์— ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค:

  • ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ: ๋ธŒ๋ผ์šฐ์ €์—์„œ ์‹คํ–‰๋˜์–ด ์ƒ๋Œ€ ๊ฒฝ๋กœ(/api/...)๊ฐ€ ํ˜„์žฌ ๋ธŒ๋ผ์šฐ์ €์˜ URL์„ ๊ธฐ์ค€์œผ๋กœ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ํ•ด์„๋ฉ๋‹ˆ๋‹ค.
  • ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ: Node.js ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰๋˜์–ด ์ƒ๋Œ€ ๊ฒฝ๋กœ๊ฐ€ ์„œ๋ฒ„์˜ ํŒŒ์ผ ์‹œ์Šคํ…œ ๊ฒฝ๋กœ๋กœ ํ•ด์„๋˜์–ด URL ํŒŒ์‹ฑ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

โœ… ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

์‹คํ–‰ ํ™˜๊ฒฝ์„ ๊ฐ์ง€ํ•˜์—ฌ ์ ์ ˆํ•œ URL์„ ์ƒ์„ฑํ•˜๋Š” ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค:

// utils/api/hospitalDetail.ts
export const getHospitalApiUrl = (id: string) => {
  // ๋ธŒ๋ผ์šฐ์ €์—์„œ ์‹คํ–‰ ์ค‘์ธ์ง€ ํ™•์ธ
  const isClient = typeof window !== 'undefined';

  if (isClient) {
    // ํด๋ผ์ด์–ธํŠธ์—์„œ๋Š” API ๋ผ์šฐํ„ฐ ์‚ฌ์šฉ
    return `/api/hospitalDetail?id=${id}`;
  } else {
    // ์„œ๋ฒ„์—์„œ๋Š” ์ ˆ๋Œ€ URL ์‚ฌ์šฉ
    const baseUrl = process.env.VERCEL_URL
      ? `https://${process.env.VERCEL_URL}`
      : 'http://localhost:3000';

    return `${baseUrl}/api/hospitalDetail?id=${id}`;
  }
};

const hospitalDetail = async (id: string) => {
  try {
    const apiUrl = getHospitalApiUrl(id);
    const res = await fetch(apiUrl);

    if (!res.ok) {
      throw new Error('Failed to fetch hospital detail');
    }
    const data = await res.json();
    const detailData = data.data[0];
    return detailData;
  } catch (error) {
    console.error('Error fetching hospital details:', error);
    throw error;
  }
};

๐ŸŽฏ ํ•ด๊ฒฐ ํฌ์ธํŠธ

  1. typeof window !== 'undefined'๋ฅผ ์‚ฌ์šฉํ•ด์„œ ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ์ธ์ง€ ์„œ๋ฒ„ ์‚ฌ์ด๋“œ์ธ์ง€ ํ™•์ธ
  2. ์„œ๋ฒ„ ํ™˜๊ฒฝ์—์„œ ์ ˆ๋Œ€ URL์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•˜๋‹ค๋ฉด ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ์—์„œ API ํ˜ธ์ถœ์ด ์‹คํŒจํ–ˆ์„๊ฒƒ
  3. ํ™˜๊ฒฝ์— ๋”ฐ๋ผ ๋‹ค๋ฅธ URL ์ „๋žต์„ ์‚ฌ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค.

๐Ÿ“ ๋ฐฐ์šด ์ 
1. Next.js์˜ ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ์™€ ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ์˜ ์‹คํ–‰ ํ™˜๊ฒฝ ์ฐจ์ด๋ฅผ ์ดํ•ด
2. typeof window๋ฅผ ํ™œ์šฉํ•œ ํ™˜๊ฒฝ ๊ฐ์ง€ ๊ธฐ๋ฒ•
3. ์„œ๋ฒ„์™€ ํด๋ผ์ด์–ธํŠธ์—์„œ์˜ URL ํ•ด์„ ์ฐจ์ด๋ฅผ ์ดํ•ด

๐Ÿ”„ ๊ฐœ์„ ๋œ ์ 
1. ์„œ๋ฒ„์™€ ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ์—์„œ ๋™์ผํ•œ API ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉ
2. API ํ˜ธ์ถœ ๋กœ์ง์„ ํ•œ ๊ณณ์—์„œ ๊ด€๋ฆฌ
3. ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ํ™œ์šฉ

profile
์ฃผ๋‹ˆ์–ด ํ”„๋ก ํŠธ์—”๋“œ ์„ฑ์žฅ๊ธฐ ๊ธฐ๋ก๊ธฐ๋ก

0๊ฐœ์˜ ๋Œ“๊ธ€