
사이트맵(Sitemap)은 검색 엔진이 웹사이트의 구조를 쉽게 이해할 수 있도록 도와주는 파일(XML 형식)이다.
https://example.com/sitemap.xml 같은 형태로 제공되고
sitemap.xml 파일을 통해 검색엔진 크롤링 최적화 및 SEO 향상 효과를 가져다 준다.
흔히 알고 있는 sitemap.xml 파일은 정적 사이트맵 방식에 해당한다.
직접 sitemap.xml을 생성해 파일 내부에 URL, 최근 수정일, 중요도 등을 지정하는 방식이다.
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://example.com/</loc>
<lastmod>2024-02-16</lastmod>
<priority>1.0</priority>
</url>
<url>
<loc>https://example.com/about</loc>
<lastmod>2024-02-10</lastmod>
<priority>0.8</priority>
</url>
<url>
<loc>https://example.com/blog</loc>
<lastmod>2024-02-12</lastmod>
<priority>0.7</priority>
</url>
<url>
<loc>https://example.com/blog/nextjs-seo</loc>
<lastmod>2024-02-10</lastmod>
<priority>0.6</priority>
</url>
<url>
<loc>https://example.com/blog/app-router-sitemap</loc>
<lastmod>2024-02-12</lastmod>
<priority>0.6</priority>
</url>
</urlset>
이 방식은 사이트의 구조가 거의 변하지 않거나 URL이 많지 않을 때 유용하지만, 다음과 같은 한계가 있다.
사이트 구조가 거의 변하지 않을 때
URL이 많지 않고, 수동을 관리할 수 있을 때
URL이 많아질 수록 관리가 어렵다.
새로운 URL이 추가될 때마다 sitemap.xml 파일을 직접 수정해야 한다.
깊은 URL 구조를 가진 사이트는 유지보수가 어렵다.
현재 Next.js 14.2.4 버전의 App Router 방식을 기준으로,
app 폴더에 sitemap.ts 파일을 작성하면 Next.js가 이를 자동으로 읽어 sitemap.xml을 동적으로 생성해준다.
이 방식에선 fetch()를 사용하여 DB 또는 API에서 동적으로 페이지 목록을 가져올 수 있다.
1. API 엔드포인트 목록 정의
const apiEndpoints = [
{ path: "/example-path", api: `https://api.sample.com/v1/example` },
{ path: "/sample-path", api: `https://api.sample.com/v1/sample` },
{ path: "/test-data", api: `https://api.sample.com/v1/test` },
];
나는 페이지네이션이 있는 페이지들의 전체 페이지 개수를 가져오는 API 목록을 배열로 정의했다.
path는 사이트에서 실제 URL 경로이고, api는 페이지 개수를 가져오는 API URL이다.
예를 들어, /example-path의 totalPages 값이 5라면
/example-path?page=1 ~ /example-path?page=5 까지 사이트맵에 포함된다.
2. API 응답 타입 정의
interface ApiResponse {
totalPages: number; // 전체 페이지 개수
}
API 응답에선 전체 페이지 개수(totalPages)를 가져온다.
3. sitemap 함수 생성
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
Next.js에서 제공하는 Metadata API를 사용해 sitemap.xml을 반환하는 함수이다.
정적 페이지와 동적 API 데이터를 활용해 페이지를 조합하여 사이트맵을 생성한다.
4. 정적 페이지 URL 생성
const BASE_URL = "https://example.com"; // BASE 도메인
const staticUrls = ["/", "/home", "/contact"];
const staticSitemap = staticUrls.map((url) => ({
url: `${BASE_URL}${url}`,
lastModified: new Date(),
}));
사이트의 기본 도메인을 BASE_URL로 설정하고,
정적 페이지 목록을 staticUrls 배열에 정의해주었다.
그리고 각 URL을 사이트맵 형식으로 변환해준다. (url + lastModified)
changefreq(변경 빈도), priority(상대적 중요도)를 설정해줄 수도 있다.참고 -> Sitemap XML 형식
5. 동적 페이지네이션 처리
const paginatedRoutes = await Promise.all(
apiEndpoints.map(async ({ path, api }) => {
try {
const response = await fetch(api, { method: "GET" });
const data: ApiResponse = await response.json();
if (!data.totalPages) {
console.warn("totalPages 값이 없습니다.", path);
return { path, totalPages: 1 }; // 기본값
}
return { path, totalPages: data.totalPages };
} catch (err) {
console.error(err);
return { path, totalPages: 1 }; // 기본값
}
}),
);
각 API를 요청해 totalPages(전체 페이지 개수) 값을 가져온다.
API 요청이 실패할 경우 기본값(totalPages = 1)을 반환해 오류를 방지한다.
6. 페이지네이션된 URL을 사이트맵에 추가
const paginatedSitemap = paginatedRoutes.flatMap(({ path, totalPages }) =>
Array.from({ length: totalPages }, (_, i) => ({
url: `${BASE_URL}${path}?page=${i + 1}`,
lastModified: new Date(),
})),
);
totalPages 값을 기반으로 ?page=1, ?page=2 등으로 URL을 생성한다.
이 코드는 모든 동적 페이지에 대해 사이트맵 URL을 자동으로 생성해준다.
결과 예시
<url>
<loc>https://example.com/example-path?page=1</loc>
<lastmod>2024-02-16</lastmod>
</url>
<url>
<loc>https://example.com/example-path?page=2</loc>
<lastmod>2024-02-16</lastmod>
</url>
...
7. 최종 사이트맵 반환
return [...staticSitemap, ...paginatedSitemap];
마지막으로 정적 URL과 동적 URL을 결합해 최종 사이트맵을 return 해준다.
이 함수가 실행되면 이제 sitemap.xml이 자동 생성된다.
import { MetadataRoute } from "next";
// API 엔드포인트 목록 예시
const apiEndpoints = [
{ path: "/example-path", api: `https://api.sample.com/v1/example` },
{ path: "/sample-path", api: `https://api.sample.com/v1/sample` },
{ path: "/test-data", api: `https://api.sample.com/v1/test` },
];
// Api 응답 타입 예시
interface ApiResponse {
totalPages: number;
}
// 동적 사이트맵을 생성하는 sitemap 함수 예시
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const BASE_URL = "https://example.com"; // BASE 도메인
// Static 페이지
const staticUrls = ["/", "/home", "/contact"];
const staticSitemap = staticUrls.map((url) => ({
url: `${BASE_URL}${url}`,
lastModified: new Date(),
}));
// Pagination 페이지
const paginatedRoutes = await Promise.all(
apiEndpoints.map(async ({ path, api }) => {
try {
const response = await fetch(api, { method: "GET" });
const data: ApiResponse = await response.json();
if (!data.totalPages) {
console.warn("totalPages 값이 없습니다.", path);
return { path, totalPages: 1 }; // 기본값
}
return { path, totalPages: data.totalPages };
} catch (err) {
console.error(err);
return { path, totalPages: 1 }; // 기본값
}
}),
);
// Pagination 페이지 URL 생성
const paginatedSitemap = paginatedRoutes.flatMap(({ path, totalPages }) =>
Array.from({ length: totalPages }, (_, i) => ({
url: `${BASE_URL}${path}?page=${i + 1}`,
lastModified: new Date(),
})),
);
return [...staticSitemap, ...paginatedSitemap];
}
1. 사이트맵 관리 자동화
2. 검색 엔진 최적화(SEO) 향상