현재 블로그 서비스를 Next.js를 통해 개발하면서 SEO 향상을 위해 사이트맵을 생성하였다.
초기에는 public 폴더 내부에 sitemap.xml 을 직접 생성하여 /, /home, /blog, /about
네 개의 경로를 사이트맵에 등록해두었다.
기존 sitemap.xml
// public/sitemap.xml
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
<url>
<loc>https://devhailey.com/</loc>
<lastmod>2025-01-15T08:17:59+00:00</lastmod>
<changefreq>daily</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://devhailey.com/home</loc>
<lastmod>2025-01-15T08:17:59+00:00</lastmod>
<changefreq>daily</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://devhailey.com/blog</loc>
<lastmod>2025-01-15T08:17:59+00:00</lastmod>
<changefreq>daily</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://devhailey.com/about</loc>
<lastmod>2025-01-15T08:17:59+00:00</lastmod>
<changefreq>daily</changefreq>
<priority>1.0</priority>
</url>
</urlset>
사이트맵에 등록해둔 네 경로는 검색엔진이 크롤링 하는 것을 볼 수 있었다.
문제는 blog 페이지에 글이 등록되면서 발생했다.
블로그에 글을 포스팅 하면 /posts/{postID}
경로에 페이지가 생성된다.
이때 포스트 페이지를 사이트맵에 등록하지 않게되면 내 포스트 글을 검색했을 때 제대로 노출되지 않을 수 있다.
그래서 블로그 내부의 포스트들을 전부 긁어와 주기적으로 사이트맵에 반영할 수 있도록 사이트맵을 수정하였다.
App Router의 sitemap.ts
App router 부터 sitemap.ts(js)
를 통해 자동으로 사이트맵을 생성해주기 때문에 간단하게 사이트맵을 생성할 수 있었다.
public 내에 있는 sitemap.xml 파일을 삭제하고 app 폴더 내부에 sitemap.ts를 생성해주었다.
기본적으로 사이트맵은 아래와 같이 작성한다.
// app/sitemap.ts
import type { MetadataRoute } from 'next'
export default function sitemap(): MetadataRoute.Sitemap {
const baseUrl = "https://devhailey.com";
return [
{
url: baseUrl,
lastModified: new Date(),
changeFrequency: "daily" as const,
priority: 1,
},
{
url: `${baseUrl}/home`,
lastModified: new Date(),
changeFrequency: "daily" as const,
priority: 1,
},
{
url: `${baseUrl}/blog`,
lastModified: new Date(),
changeFrequency: "daily" as const,
priority: 0.8,
},
{
url: `${baseUrl}/about`,
lastModified: new Date(),
changeFrequency: "weekly" as const,
priority: 0.5,
},
]
}
이제 각 포스트 url도 추가해보자
모든 포스트 데이터를 db에서 가져오는 함수를 실행하고 해당 데이터를 기반으로 url 배열을 변수에 담아줬다.
// app/sitemap.ts
// 비동기 함수로 변경
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const baseUrl = "https://devhailey.com";
// 모든 포스트 데이터 가져오기
const response = await getPostAll();
const posts = (await response.json()) as Post[];
// 블로그 포스트 URL 생성
const postsUrls = posts.map((post) => ({
url: `${baseUrl}/posts/${post._id}`,
lastModified: new Date(post.createdAt),
changeFrequency: "monthly" as const,
priority: 0.7,
}));
// 정적 페이지 URL
const staticUrls = [
{
url: baseUrl,
lastModified: new Date(),
changeFrequency: "daily" as const,
priority: 1,
},
{
url: `${baseUrl}/home`,
lastModified: new Date(),
changeFrequency: "daily" as const,
priority: 1,
},
{
url: `${baseUrl}/blog`,
lastModified: new Date(),
changeFrequency: "daily" as const,
priority: 0.8,
},
{
url: `${baseUrl}/about`,
lastModified: new Date(),
changeFrequency: "weekly" as const,
priority: 0.5,
},
];
return [...staticUrls, ...postsUrls];
}
이전에 만들어둔 정적 페이지 URL들도 따로 변수에 담아준 뒤 spread 연산자를 통해 return 해주었다.
이제 https://youresiteurl.com/sitemap.xml 으로 접속해보면 포스트 페이지들도 들어간 것을 볼 수 있다.
그런데 현재 생성한 사이트맵은 초기 빌드시에만 생성되기 때문에 주기적으로 데이터를 업데이트 해주어야한다.
최신 버전(14.2.3 ^)에서는 사이트맵 재생성을 공식적으로 지원해주기 때문에 손쉽게 ISR을 적용시킬 수 있다.
아래는 재검증 요청이 포함된 전체 코드이다.
// app/sitemap.ts
import { MetadataRoute } from "next";
import { getPostAll } from "@api/posts";
import { Post } from "@type/post";
export const revalidate = 3600; // 1시간마다 재생성
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const baseUrl = "https://devhailey.com";
// 모든 포스트 데이터 가져오기
const response = await getPostAll();
const posts = (await response.json()) as Post[];
// 블로그 포스트 URL 생성
const postsUrls = posts.map((post) => ({
url: `${baseUrl}/posts/${post._id}`,
lastModified: new Date(post.createdAt),
changeFrequency: "monthly" as const,
priority: 0.7,
}));
// 정적 페이지 URL
const staticUrls = [
{
url: baseUrl,
lastModified: new Date(),
changeFrequency: "daily" as const,
priority: 1,
},
{
url: `${baseUrl}/home`,
lastModified: new Date(),
changeFrequency: "daily" as const,
priority: 1,
},
{
url: `${baseUrl}/blog`,
lastModified: new Date(),
changeFrequency: "daily" as const,
priority: 0.8,
},
{
url: `${baseUrl}/about`,
lastModified: new Date(),
changeFrequency: "weekly" as const,
priority: 0.5,
},
];
return [...staticUrls, ...postsUrls];
}