Hello,

kok nae-ga ha-myun an-dweneun MAGIC...🧚

개발 일지

redirection하는게 이렇게 오래 걸릴 줄은 몰랐다 😬

도담 🌱 2025. 7. 9. 21:32

 /product, /product/[id]

/product와 /product/[id] 페이지를 개발하며
갑자기 마주한 페이지 이동에 대한 고민을 메모해두려합니다.

예를 들어 상품 페이지에는 여러 카테고리가 있고,
선택된 카테고리에 해당하는 상품을 보여주는 구조라면
/product/[id]의 [id]는 카테고리 ID라고 볼 수 있겠죠.

그래서 저는 /product로 접근했을 때
가장 첫 카테고리의 ID로 리다이렉트 시켜주는 구조를 만들고 싶었습니다.


🔁 처음 시도한 방식: 내부에서 [id] 페이지를 호출

처음에는 아래와 같이 /product/page.tsx에서 /product/[id]/page.tsx를 직접 호출하도록 했습니다.

// product/page.tsx
import ProductPage from '@/app/[locale]/product/[id]/page';

export default async function Page() {
  return <ProductPage />;
}

그 당시엔 "중복 코드가 없다"는 이유로 만족했지만,
나중에 보니…

  • page.tsx는 보통 SSR용 진입점으로 사용중인데 컨벤션에 어긋남!
  • /product/[id]라고 되어 있으니 ID가 필수인 경로인데, ID 없이 들어올 수 있다는 점에서 라우팅의 명확성이 떨어졌으며
  • 가장 결정적으로는, 동일한 콘텐츠가 서로 다른 URL로 노출되면 SEO에 악영향을 미친다는 사실을 알게 되었습니다.
    🔗 중복 콘텐츠 관련 SEO 가이드

🔀 리다이렉트로 리팩토링 (서버 사이드)

그래서 /product로 접근하면 가장 첫 카테고리 ID로 리다이렉트하는 구조로 바꾸었고,
Next.js의 서버 컴포넌트에서 redirect()를 사용해 처리했습니다.

// product/page.tsx
export default async function Page() {
  const { i18n } = await getT();
  const promotionCategories = await getCategories({ isPromotion: true });

  if (!promotionCategories) {
    return notFound();
  }

  redirect(`/${i18n.resolvedLanguage}/product/${promotionCategories[0].id}`);
}

그런데...
😫 화면 깜빡임이 생겼습니다...


❗ 문제 원인: 서버 컴포넌트의 redirect()는 깜빡임이 생길 수 있음

조사해보니 이건 Next.js App Router에서 알려진 이슈였습니다.
+) loading.tsx을 설정해봐도 loading이 보인 후에 똑같이 깜빡거림이 발생했습니다.

🔗 관련 GitHub 이슈 보기


🛠️ 최종 해결 방향: /product/page.tsx는 제거

결국 /product/page.tsx는 아예 제거하고,
메뉴를 생성할 때부터 첫 번째 카테고리 ID로 URL을 고정하도록 변경했습니다.

// getNavigationMenu.ts
{
  href: promotionsMenu[0].href, // 가장 첫 번째 카테고리로 연결
  label: t('menu.promotions'),
  children: promotionsMenu,
}

중복 호출이 우려되어 fetch에 next: { revalidate } 옵션을 넣어 캐싱 처리도 함께 적용했습니다.
🔗 Next.js fetch 캐싱 문서


💡 추후 고려할 수 있는 방법? canonical 설정

글을 작성하다 발견하게 되었는데 canonical 설정이 가능하니,
/product와 /product/[id]가 동일한 컴포넌트를 바라보게 하고
canonical 태그로 SEO 중복 문제를 해결하는 방식도 가능하지 않을까 생각했습니다..!

// generateMetadata.ts
export async function generateMetadata(...) {
  return {
    alternates: {
      canonical: `https://example.com/product/${id}`,
    },
  };
}

🔗 Next.js alternates 설정 문서


✅ 정리

시도한 방식결과

/product에서 [id] 페이지 직접 호출 ❌ SSR 구조와 어긋남, SEO 이슈
서버 사이드에서 redirect ❌ 깜빡임 발생
클라이언트에서 router.replace ✅ UX는 좋지만 SEO 중복 문제 발생
메뉴 구성 단계에서 첫 카테고리 ID로 URL 설정 ✅ 가장 안정적인 방식으로 선택

🧩 결론

리다이렉트를 하는데 이렇게 많은 고민을 하게 될 줄은 몰랐습니다...🥹
그래도 오랜만에 이것 저것 알아보며 재미있었네요.
좋은 방향이 있다면 꼭 말씀 부탁드려요!