Astroでブログを実装した
[!note]
記事執筆後、ブログの大幅な改修を行いました。記事の内容は一部古い情報が含まれています。近日中に記事を更新する予定です。
概要
このサイト(blog.eno1220.dev)を、AstroとTailwind CSS、Cloudflare Pagesを使って作成しました。本記事では、詳細な実装内容やプラグインの紹介を行います。
採用した技術スタック
Astro
Markdownを書いてビルドし、高パフォーマンスなサイトを実現したいと考えていたので、フレームワークとしてAstroを選択しました。
AstroはMarkdownを基本にContents Collectionを用いてコンテンツを管理したり、サイトマップやRSSフィードを生成するためのプラグインも提供したりしているので、ブログを作るのには十分な機能を持っています。
まあブログを書くだけなのにSSRするフレームワークを使って複雑な実装をしたくない、という気持ちが大きいですが…
Astro builds fast content sites, powerful web applications, dynamic server APIs, and everything in-between.
Tailwind CSS
スタイルをつける方法(CSS in JSとかCSS modulesとか)は色々あるそうで、このブログを作るにあたって色々なライブラリを調べてみましたがよく分からね〜という気持ちになったので、以前にも使ったことがあるTailwind CSSを使うことにしました。
Tailwindの使用には様々な意見があることを承知していますが、このサイトの継続性などは考えていないし、どうせ1日あれば作り直せるだろうということで、Tailwindを使うことにしました。
Tailwind CSS is a utility-first CSS framework for rapidly building modern websites without ever leaving your HTML.
Cloudflare Pages
昨年Coogleドメインがなくなる、というのでドメインをCloudflareに移行しました。それ以来、Cloudflareの各種サービス(Cloudflare WorkersやCloudflare Zero Trustなど)を使い倒していて、今回もCloudflare Pagesを使ってみました。GitHubのリポジトリを連携すると、リポジトリにpushされた際に自動でビルドしてデプロイしてくれます。また、Cloudflareが世界中に持っているCDNを使って、高速にサイトを配信できます。無料で使えるので、個人ブログには十分すぎるサービスだと思います。それにしてもCloudflareはどうやって収益を上げているんですかね…?
Build your next application with Cloudflare Pages
ライブラリ・プラグイン
記事検索
記事の検索はPagefindを導入しています。Pagefindは、静的なサイトに対する全文検索エンジンとUIを提供してくれるサービスです。
検索ページにアクセスして、記事を検索してみてください。
コードブロック
コードブロックは、Expressive Codeというプラグインを導入しています。シンタックスハイライトのほか、ファイル名や行番号、コピーボタン、diff表示などがこのプラグインでまとめて提供されているので、とても便利です。
なおAstroについてはIntegrationが提供されているので、以下のコマンドで簡単に導入できます。
pnpm astro add astro-expressive-code
pnpm add @expressive-code/plugin-line-numbers # 行番号を表示する場合↑のようなターミナル風ブロックのほか、ファイル名、行番号、コピーボタン、diff表示などがこのプラグインで提供されています。
テーマはmin-darkを使用しています。Expressive Codeのテーマはこちらから選ぶことができるほか、カスタムテーマも作成できます。
#include <stdio.h>
int main() { char str[] = "Hello, world!"; printf("%s\n", str); printf("Hello, World!\n"); return 0;}フォント
フォントは、InterとJetBrains Mono(コードブロック用)を使用しています。Fontsourceというライブラリを使用しています。
Download and self-host 1500+ open-source fonts in neatly bundled NPM packages. Access a comprehensive library of web typefaces for free.
コールアウト
コールアウトを表示するためremark-calloutを導入しています。
[!note] Callout note
Callout body
[!warning] Callout warning
Callout body
> [!note] Callout note> Callout body
> [!warning] Callout warning> Callout bodyリンク
デフォルトではリンクをクリックすると同じタブで開かれますが、外部リンクを別タブで開くようにするため、rehypejs/rehype-external-linksを導入しています。
rehypePlugins: [ [ rehypeExternalLinks, { target: '_blank', rel: ['noopener', 'noreferrer'], }, ],],また、Markdown内の単独のリンクを下記のようなリンクカードを表示するためのremarkプラグインを自作しました。
このプラグインは、ビルド時にリンク先のタイトル・説明文・faviconを取得して、リンクカードを生成します。既存のプラグインではOG画像まで取得していましたが、個人的にはfaviconだけで十分に見栄えが良いと感じたため、プラグインを自作することとしました。
42 Tokyoは学費完全無料、24時間利用可能な施設、問題解決型学習など革新的な取り組みでエンジニアを育成する機関です。
なお、実装についてはremark-link-cardを参考にさせていただきました。
RSS
RSSフィードを作成しています。Contents Collectionを使用して記事を取得し、それを元にRSSフィードを生成しています。
import rss from '@astrojs/rss';import { getCollection } from 'astro:content';
export async function GET(context: any) { const posts = await getCollection('posts'); return rss({ title: 'blog.eno1220.dev', description: 'eno1220のブログ', site: context.site, items: posts.map((post) => ({ title: post.data.title, pubDate: post.data.pubDate, description: post.data.description, link: `/posts/${post.id}/`, })), });}Add an RSS feed to your Astro site to let users subscribe to your content.
サイトマップ
サイトマップを作成しています。タグページやカテゴリページはサイトマップに含めないようにしています。
作成したサイトマップは、Google Search Consoleなどに登録することで、検索エンジンにサイトをクロールしてもらうことができます。
export default defineConfig({ // ... integrations: [ sitemap({ filter: (page) => { return !page.includes('tags') && !page.includes('categories'); }, }), ],});Astroプロジェクトで@astrojs/sitemapインテグレーションを使用する方法を学びます。
そのほか
アイコンを表示するためのライブラリです。Open Source Icon Sets - Iconifyからアイコンを選択して、以下のように記述することでアイコンを表示できます。
<Icon name="fluent:line-horizontal-4-search-16-regular" />:emoji:のような記法を絵文字に変換するプラグインです。以下のように記述することで絵文字を表示できます。
:tada: :tada: :tada:🎉 🎉 🎉
Markdown内の改行をHTMLの<br>に変換するプラグインです。以下のように記述することで改行ができます。
改行できる改行
できる
Markdown内の見出しをセクションに変換するプラグインです。作者ご本人が解説されている記事がわかりやすいです。
アウトラインアルゴリズムが変更され明示的なアウトラインの概念がなくなったため、Heading 要素を section でラップする機会も少なくなったかもしれません。私は Scrollspy などの実装が必要で作成しました。
本ブログが対応している記法については以下を参照してください。
本サイト(blog.eno1220.dev)で使用することのできるMarkdown記法のサンプルを書き方と具体例を交えながら紹介しています。
実装のポイント
OG画像

Astroの提供する静的エンドポイントを使って、ビルド時にOG画像(↑のようにTwitter等で表示される画像)を生成しています。生成には@vercel/ogを使用しています。
また、google/budouxを用いてタイトルの分かち書きを行い、日本語のタイトルでも適切なレイアウトを生成できるようにしています。
View Transitions
人によって好みが分かれると思いますが、私はページ遷移時にふんわりとしたアニメーションがあるのが良いと思い、AstroのView Transitionsのfadeを使用しています。これは、ブラウザのView Transitions APIを使用しており、またブラウザがサポートしていない場合は自動でフォールバック用の動作の制御を行ってくれます。
---import { ViewTransitions } from 'astro:transitions';---
<html> <head> <title>My Blog</title> <ViewTransitions /> </head> <body> <slot /> </body></html>また、ページ遷移直後に実行されるようなjavascriptを書きたい場合は、astro:after-swapを使用すると良いようです。(自分もよくわかっておらず、たまにバグっているのでご自身で調べていただけると幸いです)
<script> async function isGeminiAvailable() { if ((await ai.assistant.capabilities()).available === 'readily') { document.querySelector('.gemini')?.classList.remove('hidden'); } }
isGeminiAvailable(); document.addEventListener('astro:after-swap', () => { isGeminiAvailable(); });</script>シェアボタン
記事の最下部にあるシェアボタンの実装には、ウェブ共有 APIを使用しています。ユーザの選択した先にリンクを共有することができます。(OSやブラウザごとに見た目は異なります)
document.getElementById('share')?.addEventListener('click', () => { navigator.share({ title: document.title, text: document .querySelector('meta[name="description"]') ?.getAttribute('content') || '', url: location.href, });});

Gemini Nanoによる要約
記事の最下部にある「要約」ボタン1をクリックすると、Chrome組み込みのLLMであるGemini Nanoを使用して、記事の要約を表示します。
詳細は別記事にまとめましたので、そちらを参照してください。
Chromeブラウザの組み込みLLMであるGemini Nanoをブログに導入する実験を行います。生成AIにより記事の要約を行う試みの実装とその結果についてご紹介します。
最終更新日
hikaliumさんのWebサイトでGitのコミットハッシュを表示しているのを見て、フッター部分に導入しています。ブログが最後に更新された日時がわかるのが良いと思っています。
実装については、StackOverflowを参考にしています。
// ...import child_process from 'child_process';
const commitHash = child_process .execSync('git rev-parse HEAD') .toString() .trim();const commitDate = child_process .execSync('git log -1 --format=%cd') .toString() .trim();
export default defineConfig({ // ... vite: { define: { 'import.meta.env.COMMIT_HASH': JSON.stringify(commitHash), 'import.meta.env.COMMIT_DATE': JSON.stringify(commitDate), }, },});Cloudflareでビルドする際にタイムゾーンを指定しないとUTCで表示されるため注意。
<p> Commit Hash: {import.meta.env.COMMIT_HASH}</p><p> Last Update: { new Date(import.meta.env.COMMIT_DATE).toLocaleString('ja-JP', { timeZone: 'Asia/Tokyo', }) }</p>参考記事
Astroでサイトを書き直した
そのほか画像最適化・リンクカードなど
雙峰祭準備期間サイト (23年版). Contribute to sohosai/teaser23 development by creating an account on GitHub.
脚注
-
Gemini Nanoが有効になっている環境でのみ表示されます。 ↩