Astro の Content Layer を使って Zenn や Qiita に投稿した記事をブログ一覧に表示する
はじめに
Astro を使って作成している自身のブログとは別に Qiita や Zenn などに技術記事を投稿されている方も多いと思います。私もそうです。
Astro 5 から追加された Content Layer の機能を使えば、それらの外部のコンテンツデータを取り込み、自サイトのコンテンツも一緒混ぜて簡単に表示することができます。
Content Layer とはなんぞや、的な話はすでに詳しく書いてくださっている方がいたのでそちらをご覧ください。
今回は RSS フィードから取得したデータを使って Qiita や Zenn の記事を表示する方法について記載しています。おまけで Specker Deck と dev.to からの取得についても載せています。
各サービスの RSS フィードの URL
- Qiita ※
- Zenn
- SpeackerDeck
- dev.to
※ Qiita の RSS フィードの仕様で、取得できるデータが新着 4 件になっています。
実装
ライブラリの導入
Astro で RSS フィードのデータを読み込み Content Layer に組み込んで使用するにあたり、コミュニティによって公開されている@ascorbic/feed-loader の Content Loader を利用します。
npm install @ascorbic/feed-loader
content.config.ts の修正
astro の設定ファイルを修正して Loader を有効化し、各種フィードを読み込む設定を追加します。
import { defineCollection } from "astro:content";
import { feedLoader } from "@ascorbic/feed-loader";
const qiitas = defineCollection({
loader: feedLoader({
url: 'http://qiita.com/ユーザー名/feed',
}),
});
const zenns = defineCollection({
loader: feedLoader({
url: 'https://zenn.dev/ユーザー名/feed',
}),
});
const slides = defineCollection({
loader: feedLoader({
url: 'https://speakerdeck.com/ユーザー名.rss',
}),
});
const devTos = defineCollection({
loader: feedLoader({
url: 'https://dev.to/feed/ユーザー名',
}),
});
export const collections = {
slides,
qiitas,
zenns,
devTos,
};
Astro の任意のコンポーネントで利用する
Loader の設定ができたので、Astro の任意のコンポーネントで外部コンテンツを利用することが可能です。
---
import { getCollection } from 'astro:content';
type Post = {
collection: string;
id: string;
data: {
title?: string;
pubdate?: Date;
link?: string;
description?: string;
};
};
const buildPosts = async (): Promise<Post[]> => {
// Loader を使用してコンテンツを取得する
const getAllPosts = async (): Promise<Post[]> => {
const slides = (await getCollection('slides')) as Post[];
const qiitas = (await getCollection('qiitas')) as Post[];
const zenns = (await getCollection('zenns')) as Post[];
const devTos = (await getCollection('devTos')) as Post[];
return [...slides, ...qiitas, ...zenns, ...devTos];
};
// 取得したコンテンツを日付で新しい順にソートする
const sortPostsByDate = (posts: Post[]): Post[] => {
return posts.sort((a, b) => {
return (b.data.pubdate?.getTime() ?? 0) - (a.data.pubdate?.getTime() ?? 0);
});
};
return sortPostsByDate(await getAllPosts());
};
const posts = await buildPosts();
const getPostHref = (post: Post): string => {
return post.data.link ?? '';
};
---
<main>
<h1>Super Cool Blog</h1>
<ul>
{
posts.map((post) => (
<li>
<a href={getPostHref(post)}>
<span>
type:
{post.collection === 'slides'
? 'Speacker Deck'
: post.collection === 'qiitas'
? 'Qiita'
: post.collection === 'zenns'
? 'Zenn'
: post.collection === 'devTos'
? 'dev.to'
: 'local content'}
</span>
<h2>title: {post.data.title ?? ''}</h2>
<p>pubdate: {post.data.pubdate?.toISOString().slice(0, 10) ?? ''}</p>
</a>
</li>
))
}
</ul>
</main>
自サイトのブログコンテンツを外部コンテンツに混ぜて扱う
Content Layer の機能で、コンテンツが抽象化されているので外部コンテンツと自サイトのコンテンツを混ぜて扱うことも容易です。
まずは content.config.ts
を修正して、自サイトのコンテンツを読み込む設定を追加します。
以下の例では、./src/content/blog
というディレクトリ配下に .md ファイル作成しているコンテンツを読み込むようにしています。
import { defineCollection } from "astro:content";
import { feedLoader } from "@ascorbic/feed-loader";
+ const blogs = defineCollection({
+ loader: glob({ pattern: '**/[^_]*.md', base: './src/content/blog' }),
+ schema: z.object({
+ title: z.string(),
+ pubdate: z.coerce.date(),
+ }),
+ });
// 省略
export const collections = {
+ blogs,
slides,
// 省略
};
自サイトコンテンツのサンプルです。
---
title: '2025 年最初のブログだよ'
pubdate: '2025-02-13'
---
ブログ書いたよ。みんな見てね。
先ほど作成した astro ファイル内で、↑のブログコンテンツも外部コンテンツに混ぜて扱うようにしてみます。
---
export type Post = {
// 省略
data: {
title?: string;
pubdate?: Date;
link?: string;
description?: string;
};
+ body?: string; // 自サイトの md ファイルの場合は body に本文が入る
};
const buildPosts = async (): Promise<Post[]> => {
// Loader を使用してコンテンツを取得する
const getAllPosts = async (): Promise<Post[]> => {
+ const blogs = (await getCollection('blogs')) as Post[];
const slides = (await getCollection('slides')) as Post[];
// 省略
+ return [...blogs, ...slides, ...qiitas, ...zenns, ...devTos];
- return [...slides, ...qiitas, ...zenns, ...devTos];
};
// 省略
return sortPostsByDate(await getAllPosts());
};
const posts = await buildPosts();
const getPostHref = (post: Post): string => {
+ return post.collection === 'blogs' ? `blogs/${post.id}` : (post.data.link ?? '');
- return post.data.link ?? '';
};
---
// 省略
このように、すごく簡単に自サイトのコンテンツと外部に公開したコンテンツを一緒に扱えるようになります。
これでどこに記事を書こうが問題なし!
参考情報
- RSS フィード以外のデータを Content Layer で扱うための Content Loader 一覧
- 各種 Content Loader の使い方紹介
Discussion