Next.js APP에서 사이트맵 동적으로 생성하기

Next.js APP에서 사이트맵 동적으로 생성하기

개발 이야기
Junhyuk Lee

위 이미지는 그냥 퍼온 짤.

보통의 사이트맵은 sitemaps.org의 프로토콜에 맞춰 아래와 같은 형태로 작성한다.

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://aaa.com/</loc>
    <changefreq>daily</changefreq>
    <priority>1.0</priority>
  </url>
  <url>
    <loc>https://aaa.com/signin/</loc>
    <changefreq>daily</changefreq>
    <priority>0.9</priority>
  </url>
  <url>
    <loc>https://aaa.com/rewards/</loc>
    <changefreq>daily</changefreq>
    <priority>0.8</priority>
  </url>
  <url>
    <loc>https://aaa.com/policies/privacy/</loc>
    <changefreq>daily</changefreq>
    <priority>0.3</priority>
  </url>
  <url>
    <loc>https://aaa.com/policies/terms/</loc>
    <changefreq>daily</changefreq>
    <priority>0.3</priority>
  </url>
  <url>
    <loc>https://aaa.com/blog/</loc>
    <changefreq>daily</changefreq>
    <priority>0.7</priority>
  </url>
</urlset>

참고로 Next.js에서 그냥 위 처럼 정적 사이트맵을 사용하려면, app/sitemap.xml 에다가 저거 복붙하면 된다.

남들 다 아는 내용 한줄 한줄 해석해보자.

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">

이 부분이야 사이트맵을 XML 포멧으로, 그리고 sitemaps의 스키마대로 작업한다는 부분이고,

  <url>
    <loc>https://aaa.com/</loc>
    <changefreq>daily</changefreq>
    <priority>1.0</priority>
  </url>

<loc>

페이지의 URL. 반드시 https:// 또는 http:// (이제 이건 좀 쓰지 마라) 로 시작해야한다. 2,048자 까지만 지원한다. 2048자 넘는 URL을 가진 페이지라면 생각만 해도 어질어질하다..

<lastmod>

이 페이지의 마지막 수정일. W3C의 Datetime 포멧으로 작성한다. 시간은 YYYY-MM-DD 식으로 제외해도 된다. 이 수정 시간은 페이지가 마지막으로 수정된 시간을 의미하며, 사이트맵이 작성된 시간을 의미하지 않음에 유의.

<changefreq>

업데이트 빈도. 이 페이지가 보통 얼마 간격으로 업데이트 되는지를 나타낸다. always | hourly | daily | weekly | monthly | yearly | never 를 넣을 수 있다. always는 문서가 접속할 때 마다 업데이트 된다는 것을 의미하며, never는 영영 업데이트 되지 않을 아카이브된 문서 등에 사용한다. 이 changefreq 값은 그냥 힌트고, 강제성이 없다. 검색엔진 크롤러는 제멋대로 행동할것이다.

<priority>

다른 URL 대비 얼마나 중요한 페이지인지를 나타낸다. 0.0에서 1.0 사이로 적으며, 기본값은 0.5 다. 다른 웹 사이트의 페이지와는 아무 연관성이 없으므로, 검색엔진 노출 순위와 아무 상관 없다. 또한 모든 값들을 1.0으로 해봐야 SEO에 도움이 전혀 되지 않는다. 상대값이다.

자 그럼...

내 웹 사이트가 작을때는 이거 직접 입력하는게 큰 문제가 되지 않을 수 있다. 근데 이게 페이지가 한두개가 아니라면? 심지어 이 블로그 처럼 글을 쓸 때 마다 페이지가 늘어난다면? 글 쓸 때 마다 사이트맵 수정할건가? 개발자는 최대한 게을러야 한다. 컴퓨터를 노예로 부려서 일을 쉽고 편하게 하는 방법을 연구하는 사람이 개발자다.

이제 Next.js의 동적 사이트맵 생성 기능을 이용해보자.

Next.js 공식 문서의 Generate a Sitemap 문서을 참조하자.

import { MetadataRoute } from 'next';
 
export default function sitemap(): MetadataRoute.Sitemap {
  return [
    {
      url: 'https://acme.com',
      lastModified: new Date(),
    },
    {
      url: 'https://acme.com/about',
      lastModified: new Date(),
    },
    {
      url: 'https://acme.com/blog',
      lastModified: new Date(),
    },
  ];
}

뭐야 이게 다네.. 하면 좀 별로고.. 적용하면서 알아낸 점들을 공유해본다.

  1. public/sitemap.xml이 있을 경우 app/sitemap.ts 는 동작하지 않는다. public에 남은 찌꺼기를 지워라.
  2. react-query 같은 클라이언트 펑션은 사용할 수 없다. 너무 싫고, 구리구리한 fetch를 써야한다.
  3. changeFrequency와 priority는 아직 지원하지 않는다. 어떤 착한 형이 PR 을 만들어놓은게 보인다.
  4. 공식문서에 보면, 여러개의 사이트맵을 동시생성할 수 있는 기능을 만드는 중이라고 한다. 여기여기서 그 흔적을 찾을 수 있었다. generateSitemaps() 라는 함수를 한창 만드는 중인듯.

내 블로그 글을 땡겨와서 사이트맵으로 만드는 코드는 다음과 같다.

export const getPosts = () => {
  return fetch(`${process.env.NEXT_PUBLIC_BASE_FETCH_URL}/api/blog/posts`, {
    next: { revalidate: 60 * 10 }, // 10분 캐시
  })
    .then((res) => {
      if (!res.ok) {
        return Promise.reject();
      }
      return res.json();
    })
    .catch(() => {
      return [];
    });
};

const Sitemap = async (): Promise<MetadataRoute.Sitemap> => {
  const posts: FBBlogPost[] = await getPosts();

  const blogPosts = posts.map((post) => ({
    url: `https://miriya.vercel.app/blog/${post.id}`,
    lastModified: new Date(),
  }));

  return [
    {
      url: 'https://miriya.vercel.app',
      lastModified: new Date(),
    },
    {
      url: 'https://miriya.vercel.app/profile',
      lastModified: new Date(),
    },
    {
      url: 'https://miriya.vercel.app/idols',
      lastModified: new Date(),
    },
    {
      url: 'https://miriya.vercel.app/cameras',
      lastModified: new Date(),
    },
    {
      url: 'https://miriya.vercel.app/pentax',
      lastModified: new Date(),
    },
    {
      url: 'https://miriya.vercel.app/pentax/slr',
      lastModified: new Date(),
    },
    {
      url: 'https://miriya.vercel.app/pentax/dslr',
      lastModified: new Date(),
    },
    {
      url: 'https://miriya.vercel.app/mycar',
      lastModified: new Date(),
    },
    {
      url: 'https://miriya.vercel.app/minihome',
      lastModified: new Date(),
    },
    ...blogPosts,
  ];
};

export default Sitemap;

async로 데이터 불러와서 돌리다보니 Promise 한꺼풀 씌워준거 외에 특이사항은 없다.

아직 사이트 복잡도가 낮아서 뭐 강좌라고 하기에도 뭐한걸 적게 되었는데, 이후에 기능이 추가되는대로 업데이트 할 예정이다. 이 문서를 작성하는 시점에서 Next.js 버전은 v13.4.3-canary.0 이다.

Loading...