Next.js: Sitemap 분할 생성과 Redirect 문제 해결하기

camel·2024년 7월 28일
6
post-thumbnail

✅ 개요

SEO (검색 엔진 최적화)는 웹사이트 성장에 필수적인 요소입니다.
최근에 SEO에 대해 집중적으로 공부하면서 이를 적용해볼 수 있는 기회가 있었는데요.

이 과정에서 겪은 주요 이슈에 대해 정리해보려고 합니다.
주요 이슈는 Sitemap과 Redirection 관련 문제였습니다.

Next.js 프레임워크를 사용하여 2만 개 이상의 Sitemap을 생성하는 과정과 Redirect 관련 이슈 해결 경험을 다룰 예정입니다.

적용하는 과정에서 구글의 SEO 가이드라인과 Next.js 공식 문서를 중심으로 작성하였습니다.
하단에 링크를 첨부하였으니 참고하시면 좋을 겄 같습니다.


🤔 sitemap이란?

sitemap은 웹사이트의 구조를 크롤링 봇에게 알려주는 일종의 지도 역할을 합니다.
웹사이트를 효과적으로 크롤링하는 데 도움을 줍니다. 특히 수백 페이지 이상의 대규모 사이트에서 효율적입니다.

이슈 1) sitemap 2만개 이상의 데이터 처리하기

Next.js 프레임워크 기반 웹사이트에서 2만 개가 넘는 데이터를 Sitemap으로 생성해야 하는 상황이었습니다.

초기에는 빌드 시점에 생성하는 방법을 고려했지만, 빌드 시점에 DB 접근이 불가능하여 동적 생성이 필요했습니다.
(빌드하는 방식으로 했다면, 시간이 오래 걸려서 문제가 있었을 것 같습니다.)

첫 번째 시도: 단일 페이지 Sitemap 생성 실패 ❌

가이드라인에는 5만개까지 가능하고, 일정이상 용량이 넘으면 불가능하다고 적혀있었습니다.
제가 생각하기에 한번에 많은 데이터를 가져오고, 이를 그리는 것은 브라우저에게 무거울 것이라고 생각했습니다.

직접 해보는 것이 좋을 것 같아서 데이터를 한 페이지에 담아 Sitemap을 생성하였습니다.
예상대로 과도한 데이터 처리로 인해 페이지가 멈추고 에러가 발생하는 문제가 발생했습니다.

이 문제를 해결하기 위해 Next.js 공식 문서의 Sitemap 관련 내용을 참고하여, 데이터를 분할하여 처리하는 방식으로 접근했습니다.

generateSitemaps (App Router 방식)

Next.js에서는 대용량 Sitemap 처리를 위해 generateSitemaps 메서드를 추천합니다. 이 방식은 Sitemap을 여러 페이지로 나누어 처리합니다.

App Router를 사용하는 경우, 다음과 같이 구현할 수 있습니다:

export async function generateSitemaps() {
  return [{ id: 0 }, { id: 1 }, { id: 2 }, { id: 3 }]
}

export default async function sitemap({
  id,
}: {
  id: number
}): Promise<MetadataRoute.Sitemap> {
  const start = id * 50000
  const end = start + 50000
  const products = await getProducts(
    `SELECT id, date FROM products WHERE id BETWEEN ${start} AND ${end}`
  )
  return products.map((product) => ({
    url: `${BASE_URL}/product/${id}`,
    lastModified: product.date,
  }))
}

이 방법을 사용하면 /product/sitemap/1.xml과 같은 형식으로 여러 Sitemap 파일이 생성되어, 대량의 URL을 효과적으로 관리할 수 있습니다.

하지만 Page Router를 사용 중이어서 이 방법을 직접 적용할 수 없었습니다. 따라서 다른 해결책을 모색해야 했습니다.

Page Router 환경에서 Sitemap 생성 시도: 동적 라우팅 적용 ❌

generateSitemaps 함수의 핵심 아이디어인 Sitemap 분할 방식을 Page Router 환경에 적용하기 위해 동적 라우팅을 활용했습니다.

sitemap-[id].xml.ts 파일을 생성하여 각 페이지별 Sitemap을 생성하고, 페이지 번호를 동적으로 할당했습니다.

이슈 2) 동적 라우팅 헤더 변경 이슈

Sitemap 파일의 Content-Type이 변경되지 않는 이슈가 있었습니다.

res.setHeader('Content-Type', 'text/xml') 코드를 통해 Content-Type을 변경하려 했으나, 동적 라우팅 환경에서는 헤더 설정이 제대로 작동하지 않았습니다.

이로 인해 동적으로 생성한 Sitemap 파일의 Content-Type이 text/html로 설정되어 크롤러가 Sitemap을 제대로 인식하지 못하는 문제가 발생했습니다.

이 문제를 해결하기 위해 Sitemap 생성 방식을 다시 고민해야 했습니다.

정적 페이지와 파라미터를 활용한 Sitemap 생성 해결 방법 ⭕

동적 라우팅에서 발생한 헤더 문제를 해결하기 위해, 정적 페이지 생성을 사용하였습니다.
이 방식을 유지하면 헤더를 변경할 수 있고, 파라미터를 통해 내부 로직을 핸들링할 수 있었습니다.

이를 통해 헤더 설정 문제를 해결하고 페이지네이션을 구현할 수 있었습니다.

<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  ${Array.from({ length: totalPages }, (_, i) => i + 1).map(page => `
    <sitemap>
      <loc>${BASE_URL}${BASE_PATH}/sitemap.xml?page=${page}</loc>
      <lastmod>${new Date().toISOString()}</lastmod>
    </sitemap>
  `).join('')}
</sitemapindex>

이러한 방식을 통해 Page Router 환경에서도 파라미터를 활용하여 동적으로 Sitemap을 생성할 수 있었습니다.

👀 Sitemap Index 파일을 통한 크롤러 안내

한가지 의문이 생겼습니다.
파라미터를 사용하는 Sitemap을 크롤러가 어떻게 알 수 있을까요?

그래서 이에 대해 알려주는 방법이 필요했습니다.
먼저 sitemap이 어떻게 구성되어있는지 알려주기 위해 sitemapindex 태그를 활용했습니다.

<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <sitemap>
    <loc>https://www.example.com/sitemap1.xml.gz</loc>
  </sitemap>
  <sitemap>
    <loc>https://www.example.com/sitemap2.xml.gz</loc>
  </sitemap>
</sitemapindex>

위처럼 작성하여 현재 사이트의 Sitemap이 어떻게 구성되었는지 알려주는 파일을 만들었습니다.
크롤 봇이 이파일을 인식 할 수 있도록, robots.txt도 수정해주었습니다.

robots.txt 설정

Sitemap Index 파일의 경로를 명시하여 크롤러가 Sitemapindex 파일이 있다는 것을 알려주었습니다.

User-agent: *
Allow: /

Sitemap: https://www.example.com/sitemap-index.xml.ts

이를 통해 크롤러가 웹사이트의 sitemap 구조에 대해 이해할 수 있게 작성하였습니다.
sitemap index에서는 총 500개의 파일까지 인식할 수 있다고하니 참고해주시면 좋을 것 같습니다.


다음으로는 redirect에 관한 문제가 있었습니다.
먼저 redirect에 관한 중요도에 관한 이야기를 먼저 해보려고 합니다.

SEO Redirect?

URL Redirect는 기존 URL을 새로운 URL로 연결하여 웹사이트 운영 및 SEO에 중요한 역할을 합니다.

사이트 이전, URL 통합, 페이지 삭제 등 다양한 상황에서 활용되며, 검색 엔진이 웹페이지를 올바르게 인식하고 색인하도록 돕는 역할을 합니다.

Redirect의 중요성

검색 엔진은 www.example.comexample.com처럼 유사한 URL을 별개의 사이트로 인식할 수 있습니다.

이 경우 트래픽 분산 문제가 발생하여 SEO 효과를 저해할 수 있습니다. 따라서 Redirect를 통해 URL을 통일하고, 검색 엔진이 사이트 콘텐츠를 정확하게 파악하도록 유도해야 합니다.

Permanent Redirection vs. Temporary Redirection

Redirect는 크게 Permanent Redirection과 Temporary Redirection 두 가지 유형으로 나뉩니다.

  • Permanent Redirection (301, 308): URL 변경이 영구적인 경우 사용하며, 검색 엔진은 새로운 URL을 검색 결과에 표시합니다.
  • Temporary Redirection (302, 303, 307): 일시적인 URL 변경(A/B 테스트, 페이지 리펙토링 등)에 사용하며, 검색 엔진은 원래 URL을 검색 결과에 유지합니다.

표로 정리하면 아래와 같습니다.

리다이렉션 유형상태 코드검색 결과 표시사용 기준검색 엔진 인식
Permanent301, 308새 URL변경이 영구적인 경우강한 신호로 인식
Temporary302, 303, 307원래 URL- 일시적인 변경
- 페이지 리펙토링
약한 신호로 인식
원래 URL 복구 시 정상 안내

검색 엔진은 Permanent Redirection을 강력한 신호로 인식하여 새로운 URL을 빠르게 색인하고, Temporary Redirection은 일시적인 변경으로 간주하여 원래 URL의 정보를 유지합니다. 따라서 Redirect 유형 선택 시 변경 목적과 기간을 고려하여 적절한 상태 코드를 사용해야 합니다.

Next.js에서 301 Redirect 지원 문제 해결

프로젝트 요구사항은 www 접속 시 baseUrl로 301 Redirect하는 것이었습니다. nginx 설정 등 변경이 불가능한 상황이어서 next.config.js를 활용해야 했습니다.

이슈) 308 Redirect ❓

next.config.js에서 permanent: true 옵션을 설정하여 301 Redirect를 시도했지만, 실제로는 308 Redirect가 발생하는 문제가 발생했습니다.

Next.js 공식 문서를 확인한 결과, Next.js는 기본적으로 301 Redirect 대신 307(임시), 308(영구) Redirect를 사용한다는 사실을 알게 되었습니다.

이는 기존 301, 302 Redirect가 요청 메서드를 GET으로 변경하는 문제를 해결하기 위한 조치였습니다.

👀 308 Redirect 사용에 대한 검증

요구사항은 전통적인 301 Redirect였지만, Next.js에서 301을 직접 지원하지 않기 때문에 308 Redirect를 사용해도 문제가 없는지 확인해야 했습니다.

Google 검색 엔진 최적화(SEO) 가이드라인을 확인한 결과, 301과 308 Redirect는 동일하게 평가된다는 내용을 확인할 수 있었습니다. 또한, 해외 블로그에서도 308 Redirect 사용에 문제가 없다는 정보를 찾을 수 있었습니다. (참고: next.js redirect 308 관련 블로그)

문제 해결 및 소통 ⭕

이러한 정보를 바탕으로 308 Redirect를 사용해도 SEO에 영향이 없음을 확인하고, 관련 내용을 담당자에게 설명하여 문제를 해결했습니다.

이번 경험을 통해 Next.js의 Redirect 동작 방식과 301, 308 Redirect의 차이점을 명확하게 이해할 수 있었고, 제한적인 환경에서도 문제를 해결하는 방법을 배우는 좋은 기회가 되었습니다.


SEO 전문가와 협업하는 경험

SEO 전문가와 함께 협업하며 실제 프로젝트에 SEO를 적용해보는 경험은 제가 원하던 경험이었습니다.

일이 끝나고 난 이후에는 SEO 전문가 분께서 일처리도 빠르고, 자발적으로 조사까지하고, 적용해주시는 부분이 좋았다며 칭찬까지 해주셔서 열심히한 보람을 느낄 수 있었습니다 😀

앞으로도 이런 말을 더 들을 수 있도록 정진하겠습니다. 감사합니다~


관련 내용 링크:

redirect
https://developers.google.com/search/docs/crawling-indexing/301-redirects?hl=ko
next.js redirect 308 이유
https://nextjs.org/docs/pages/api-reference/next-config-js/redirects?hl=ko-KR
next.js redirect 308 관련 블로그
https://robertmarshall.dev/blog/how-to-permanently-redirect-301-308-with-next-js/
redirect guide
https://nextjs.org/docs/app/building-your-application/routing/redirecting#redirects-in-nextconfigjs

sitemap
https://developers.google.com/search/docs/crawling-indexing/sitemaps/large-sitemaps?hl=ko
sitemap(app router)
https://nextjs.org/docs/app/api-reference/functions/generate-sitemaps
https://nextjs.org/docs/app/api-reference/file-conventions/metadata/sitemap
sitemap(page router)
https://nextjs.org/learn-pages-router/seo/crawling-and-indexing/xml-sitemaps

robots.txt
https://developers.google.com/search/docs/crawling-indexing/robots/create-robots-txt?hl=ko

profile
개발과 일을 잘하고 싶은 사람

0개의 댓글