한국어 페이지랑 영어 페이지를 완전 별개 문서로 색인되게 놔뒀다가, 구글이 이걸 중복 콘텐츠로 오해할 수도 있겠다 싶어서 부랴부랴 고쳤던 기억이 나요. img-compressor 저장소app/sitemap.ts 얘기예요.

같은 페이지의 언어별 URL을 한 항목으로 묶었어요

export default function sitemap(): MetadataRoute.Sitemap {
  return locales.flatMap((locale) =>
    routes.map((route) => ({
      url: \`\${baseUrl}/\${locale}\${route.path}\`,
      lastModified: contentLastModified,
      changeFrequency: route.changeFrequency,
      priority: route.priority,
      alternates: {
        languages: {
          "ko-KR": \`\${baseUrl}/ko\${route.path}\`,
          "en-US": \`\${baseUrl}/en\${route.path}\`,
          "x-default": \`\${baseUrl}/ko\${route.path}\`,
        },
      },
    }))
  );
}

locales.flatMap은 언어 수만큼 전체 라우트를 반복해서 URL을 만들어요. 이때 각 URL 항목에 alternates.languages를 같이 넣어서, 검색 엔진이 "이 URL은 다른 언어로도 있다"는 관계를 사이트맵만 보고 알 수 있게 했어요. 이게 없으면 한국어 페이지랑 영어 페이지가 완전히 다른 문서로 색인돼서, 같은 내용을 중복 콘텐츠로 오해할 여지가 생기더라고요.

x-default가 왜 필요했나

이 코드는 x-default를 항상 한국어 경로로 지정해요. x-default는 사용자 언어나 지역이 등록된 언어 목록이랑 정확히 안 맞을 때, 검색 엔진이 어떤 버전을 먼저 보여줄지 판단하는 기준이에요. 이 프로젝트는 기본 언어가 한국어(defaultLocale: Locale = "ko")라서, x-default도 거기에 맞춰서 기본 언어 설정이랑 사이트맵 신호가 안 어긋나게 했어요.

다만 이 사이트맵은 lastModifiedcontentLastModified라는 고정 날짜 하나로 모든 라우트에 똑같이 적용해요. 실제로 페이지마다 갱신 날짜가 다르다면, 페이지별 진짜 수정일을 반영하는 쪽이 더 정확한 신호가 될 거예요. 이건 코드에서 확인한 지금 구현의 한계로 남겨둘게요.