stock.dev
Astro + microCMSのブログにRSSフィードを実装した

Astro + microCMSのブログにRSSフィードを実装した

·6 min read

Astro + microCMS + Cloudflare PagesのブログにRSSフィードを実装した。 公式パッケージ@astrojs/rss/rss.xmlを配信しつつ、フッターやAboutの「RSS」リンクからは/rss/というHTMLの説明ページに案内する二段構えにしてみた。

子供の闘病ブログは親世代の訪問が多いので、RSSリーダーの説明を入れるとより親切かなと考えた。

公開URL:


構成

RSSリーダー ─────→ /rss.xml          (XML、本物のフィード)

                       │ <link rel="alternate"> で autodiscovery

人間の読者  ─────→ /rss/             (HTML、説明とURLコピー導線)
   ↑                   ↓
フッター/About    URLコピー + リーダー紹介

機械(RSSリーダー)は/rss.xmlを直接読みに行く。人間の読者がフッターやAboutから踏んだときは、まずHTMLの説明ページ/rss/を経由する。


/rss.xmlの配信

公式パッケージを使う。

npm install @astrojs/rss

src/pages/rss.xml.tsを作成する。

import rss from '@astrojs/rss';
import type { APIContext } from 'astro';
import { getArticles, articlePath, extractExcerpt } from '../lib/microcms';
import { SITE } from '../consts';
 
export async function GET(context: APIContext) {
  const { totalCount } = await getArticles({ limit: 1 });
 
  const items =
    totalCount === 0
      ? []
      : (
          await getArticles({ limit: totalCount, orders: '-day' })
        ).contents.map((article) => ({
          title: article.title,
          pubDate: new Date(article.day),
          description: extractExcerpt(article.body, 200),
          content: article.body,
          link: articlePath(article),
        }));
 
  return rss({
    title: SITE.title,
    description: SITE.description,
    site: context.site ?? SITE.url,
    items,
    xmlns: { content: 'http://purl.org/rss/1.0/modules/content/' },
    customData: '<language>ja-JP</language>',
  });
}

microCMSのlimit挙動

microCMSのリストAPIはlimitがデフォルト10、引数で指定しても最大100まで。全件取りたいときはまずlimit: 1totalCountを取得し、その値をlimitに渡している。 ソートはorders: '-day'で記録日の新しい順。

descriptionとcontent:encodedを両方出す

@astrojs/rssのitemに渡せるフィールドのうち、本文を載せられるのは以下の2つ。

  • description: 抜粋。HTMLタグをstripして200字に切っている
  • content: 本文HTMLそのまま。出力上はcontent:encoded要素になる

両方含めると、フィードリーダー上で抜粋一覧と全文表示の両方ができる。

content:encodedのためのxmlns宣言

content:encodedはRSS 2.0コア仕様ではなく拡張仕様で、namespace宣言が必要になる。@astrojs/rssではxmlnsオプションで渡す。

xmlns: { content: 'http://purl.org/rss/1.0/modules/content/' },

pubDateにはdayを渡す

microCMSはシステムフィールドpublishedAt(記事の公開日時)を持っているが、このブログでは記録日を表すカスタムフィールドdayを別に持っている。(⇦闘病ブログという性質のため記録日を大切にしたかったので。)pubDateにはpublishedAtではなくdayを渡している。

pubDate: new Date(article.day),

autodiscoveryのリンクをheadに追加

RSSリーダー(Feedly、Inoreader等)はサイトのトップURLから<link rel="alternate">をたどってフィードを検出する。BaseLayout.astro<head>に1行追加する。

<link
  rel="alternate"
  type="application/rss+xml"
  title={SITE.title}
  href="/rss.xml"
/>

これでhttps://west-blog.pages.dev/をリーダーに登録するだけで/rss.xmlが検出される。


フッター/AboutにRSSリンクを置く

Footer.astroに「RSS」リンクを置く。リンク先は/rss.xmlではなく次に作る/rss/

<a href="/rss/" aria-label="RSSフィードを購読">
  <svg viewBox="0 0 24 24" width="12" height="12" aria-hidden="true">
    <circle cx="5.5" cy="18.5" r="2.5" />
    <path d="M3 11v4c4.97 0 9 4.03 9 9h4c0-7.18-5.82-13-13-13z" />
    <path d="M3 3v4c9.39 0 17 7.61 17 17h4C24 12.85 14.85 3 3 3z" />
  </svg>
  <span>RSS</span>
</a>

Aboutページにも「新しい記録を受け取る」セクションを追加してみた。


/rss/着地ページ

src/pages/rss.astroを作成する。中身は以下。

  • RSSアイコン(56px)
  • フィードURLの表示とコピーボタン
  • RSSリーダーの説明
  • 推奨リーダー(Feedly / Inoreader / NetNewsWire)
---
import BaseLayout from '../layouts/BaseLayout.astro';
import { SITE } from '../consts';
 
const feedUrl = new URL('/rss.xml', SITE.url).toString();
---
 
<BaseLayout title="RSSフィードを受け取る">
  <article>
    <header>
      <p class="eyebrow">RSS</p>
      <h1>新しい記録を受け取る</h1>
      <svg viewBox="0 0 24 24" width="56" height="56" aria-hidden="true">
        <circle cx="5.5" cy="18.5" r="2.5" />
        <path d="M3 11v4c4.97 0 9 4.03 9 9h4c0-7.18-5.82-13-13-13z" />
        <path d="M3 3v4c9.39 0 17 7.61 17 17h4C24 12.85 14.85 3 3 3z" />
      </svg>
    </header>
 
    <section>
      <p>フィードURL</p>
      <div>
        <code id="rss-feed-url">{feedUrl}</code>
        <button type="button" data-target="rss-feed-url">
          <span class="default">URLをコピー</span>
          <span class="done" aria-hidden="true">コピーしました</span>
        </button>
      </div>
    </section>
 
    <section>
      <h2>RSSリーダーって?</h2>
      <ul>
        <li><strong>Feedly</strong> — Web / iOS / Android</li>
        <li><strong>Inoreader</strong> — Web / iOS / Android</li>
        <li><strong>NetNewsWire</strong> — Mac / iPhone</li>
      </ul>
    </section>
  </article>
</BaseLayout>

コピーボタンのスクリプト

<script>
  const buttons = document.querySelectorAll('.rss-page__copy');
  buttons.forEach((btn) => {
    btn.addEventListener('click', async () => {
      const targetId = btn.dataset.target;
      const el = document.getElementById(targetId);
      const text = (el.textContent ?? '').trim();
      try {
        await navigator.clipboard.writeText(text);
        btn.classList.add('is-done');
        setTimeout(() => btn.classList.remove('is-done'), 2000);
      } catch {
        const range = document.createRange();
        range.selectNodeContents(el);
        const sel = window.getSelection();
        sel?.removeAllRanges();
        sel?.addRange(range);
      }
    });
  });
</script>

navigator.clipboardはsecure context(HTTPS、またはlocalhost)でのみ使える。HTTPでアクセスしたローカル、cross-originのiframe等の環境ではrejectされるので、catchでテキスト範囲を選択状態にするフォールバックを入れている。


参考リンク: