스벨트킷 블로그에 RSS와 사이트맵 추가하기

아래 내용은 1.0.0-next.406 이후 버전을 반영하여 수정되었습니다. (2022-09-11 기준 최신 버전인 1.0.0-next.480 에서 잘 작동하는걸 확인했습니다)

1.0.0-next-405 또는 그 이전 버전에서 만든 프로젝트를 마이그레이션하려면 아래 링크를 참고해주세요.


스벨트킷으로 만든 블로그에 RSS 및 사이트맵을 추가하는 방법을 설명합니다.

RSS란

RSS는 “Really Simple Syndication”의 약자이며 XML 문서의 일종입니다. 블로그 등의 웹사이트에서 RSS를 지원하면 https://feedly.com 같은 RSS 구독 서비스로 해당 사이트를 구독할 수 있게 해줍니다.

구현

RSS 형식에 맞는 XML 문자열을 생성해주는 라이브러리를 설치합니다.

npm i -D feed

이제 RSS 문서를 서빙하는 엔드포인트를 추가해야 합니다. 저는 src/routes/rss.xml 디렉터리를 만들고 그 안에 /+server.ts 파일을 추가하여 /rss.xml 엔드포인트를 만들었습니다.

import { Feed } from 'feed'

import conf from '$lib/conf'
import { getArticleMetas } from '$lib/server/article'

import type { RequestHandler } from './$types'

export const prerender = true

export const GET: RequestHandler = async () => {
  const posts = await getArticleMetas('src/routes/posts')
  const feed = new Feed({
    title: conf.title,
    description: conf.description,
    id: conf.url,
    language: 'ko',
    copyright: conf.copyright,
    author: {
      name: conf.authorName,
      email: conf.authorEmail,
      link: conf.authorTwitter,
    },
  })
  posts.forEach(p => {
    feed.addItem({
      title: p.title,
      id: p.id,
      link: `${conf.url}/posts/${p.id}`,
      description: p.summary,
      date: new Date(p.publishedAt),
    })
  })

  return new Response(feed.rss2(), { headers: { 'Content-Type': 'text/xml; charset=utf-8' } })
}

위 코드에서 임포트하고 있는 getArticleMetas() 함수에 대한 내용은 스벨트킷으로 블로그 만들기 글을 참고해주세요.

링크 추가하기

만든 XML의 URL을 아래와 같이 레이아웃에 추가해주면 각종 로봇이 RSS 피드를 발견할 수 있게 됩니다. 스벨트킷의 정적 생성 기능이 /rss.xml 파일을 생성하게 하기 위해서도 필요합니다.

<svelte:head>
  <link rel="alternate" type="application/rss+xml" title="RSS" href="/rss.xml" />
</svelte:head>

정적 사이트에 배포하기

표준에 의하면, RSS 문서의 MIME 타입은 application/rss+xml입니다. 아지만 무슨 이유에서인지 이 MIME 타입을 쓰면 크롬에서 이 파일을 XML로 인식하지 못하는 문제가 있습니다. application/xml 또는 text/xml을 사용하면 잘 작동합니다. RFC7303 Section 4.3에 따르면 text/xmlapplication/xml의 별칭입니다.

이에 따라 위 코드에서도 응답 헤더에 Content-Type: text/xml; charset=utf-8을 추가했습니다. 하지만 어차피 이 블로그는 정적 사이트 생성static site generation을 사용하기 때문에 스벨트킷에서 응답 헤더를 지정해도 별 소용이 없습니다.

정적 파일을 서빙할 때 몇몇 웹 서버는 파일 확장자에 기반해서 Content-Type 헤더를 추가해주기도 합니다. 이런 특성을 확용하기 위해 엔드포인트 주소를 /rss가 아닌 /rss.xml로 지정했습니다.

다만, 이 블로그를 배포하고 있는 CloudFlare Pages에서는 확장자가 xml인 파일도 그냥 text/html로 서빙하고 있었습니다. 다행히도 검색을 해보니 _headers라는 파일을 만들어주면 CloufFlare Pages에도 원하는 커스텀 헤더를 추가할 수 있었습니다.

/rss.xml
  Content-Type: text/xml; charset=utf-8

사이트맵 만들기

기왕 RSS를 만들었으니 사이트맵 프로토콜을 따르는 사이트맵 파일도 만들어주면 좋겠습니다. 사이트맵을 만들면 검색엔진최적화에 도움이 됩니다.

src/sitemap.xml/+server.ts를 생성하고 아래 내용을 적습니다.

import conf from '$lib/conf'
import { getArticleMetas } from '$lib/server/article'

import type { RequestHandler } from './$types'

export const prerender = true

export const GET: RequestHandler = async () => {
  const posts = await getArticleMetas('src/routes/posts')
  const links = posts.map(
    p => `
    <sitemap>
      <loc>${conf.url}/posts/${p.id}</loc>
      <lastmod>${p.modifiedAt}</lastmod>
    </sitemap>
  `,
  )
  const xml = `
    <?xml version="1.0" encoding="UTF-8"?>
    <sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    ${links.join('')}
    </sitemapindex>
  `.trim()
  return new Response(xml, { headers: { 'Content-Type': 'text/xml; charset=utf-8' } })
}

보통은 XML을 생성할 때 위와 같이 문자열을 직접 다루기보다는 XML 생성 라이브러리를 사용하는 편이 좋지만 사이트맵 XML은 워낙 단순하니까 그냥 문자열을 직접 만들었습니다.

RSS와 마찬가지로 이 파일도 text/xml로 서빙하기 위해 /static/_headers에 커스텀 헤더를 추가합니다.

/rss.xml
  Content-Type: text/xml; charset=utf-8
/sitemap.xml
  Content-Type: text/xml; charset=utf-8

마지막으로, 레이아웃에도 링크를 추가해줍니다.

<svelte:head>
  <link rel="alternate" type="application/rss+xml" title="RSS" href="/rss.xml" />
  <link rel="sitemap" type="application/xml" title="Sitemap" href="/sitemap.xml" />
</svelte:head>

마치며

이 글에서 설명한 코드는 이 블로그에도 적용되어 있습니다. 소스 코드가 공개되어 있으니 참고해주세요.

관련 글