Astroの便利なプラグインやライブラリ

投稿日
2025年1月8日
Astroの便利なプラグインやライブラリの見出し画像
目次

はじめに

本ブログはAstroで構築されています。 実装するにあたって有用だったライブラリやインテグレーションを紹介します。

astro
5.1.1
tailwind
3.4.17

インテグレーション

Tailwind

公式のインテグレーションをそのまま使って導入しています。

TailwindもNestingでCSSに記述できるように、下記オプションを追記。

astro.config.mjs
export default defineConfig({
integrations: [
tailwind({
nesting: true,
}),
],
});

typography

proseクラスを割り当てた要素に対して、記事っぽいスタイルを適用してくれるTailwindプラグインです。 見出し要素(h1h6)やpタグなどにmarginやfont-size、colorを割り当ててくれます。

下記のように全体のサイズ変更やprose系のスタイル変更もクラスで行えて便利です。

<div class='prose lg:prose-lg prose-sm prose-h1:text-amber-400'>
<h1>見出し</h1>
<p>本文</p>
</div>

ただ、サイト全体のスタイリング修正をクラスで完全に対応させるのは、メンテナンスが難しいので、CSSファイルでまとめて上書きしています。

/* 記事ページでimport */
.prose {
h1 {
color: #f00;
}
}

cn()関数

下記記事を参考に、clsxtailwind-mergeモジュールを使ったヘルパー関数を定義しています。

クラス名の振り分けが簡単になり、コードも読みやすくなります。

MDX

公式のインテグレーションをそのまま使って導入しています。

Sitemap

公式のインテグレーションで導入しています。

ドキュメント通りintegrationsに追記すれば導入完了です。

あとは、共通<head>への追記とrobots.txtを設置すればOKです。

<head>
<link rel="sitemap" href="/sitemap-index.xml" />
</head>

robots.txtにドメインをハードコーディングしたくない場合は、静的エンドポイントとして実装できます。

src/pages/robots.txt.ts
import type { APIRoute } from 'astro';
const getRobotsTxt = (sitemapURL: URL) => `
User-agent: *
Allow: /
Sitemap: ${sitemapURL.href}
`;
export const GET: APIRoute = ({ site }) => {
const sitemapURL = new URL('sitemap-index.xml', site);
return new Response(getRobotsTxt(sitemapURL));
};

siteの値には、astro.config.mjssiteプロパティが入ってくるので、ドメインが変わってもrobots.txtの内容を変更する必要がなくなります。

Expressive Code

コードブロックのシンタックスハイライタです。diff表現や行コメント、ターミナルのフレーム等が標準で備わっていているのでこちらを採用。

インストールのドキュメントにある通り、Astro用のインテグレーションも用意されています。

Terminal window
npx astro add astro-expressive-code

MDXインテグレーションを使っている場合には、mdx()よりも前にexpressiveCode()を追記する必要があります。

テーマ一覧

スタイル調整

styleOverridesプロパティで上書きできます。ドキュメントから該当するパラメータを探して設定してください。 例えば、フレームの影を消したい場合は下記のように設定します。

astro.config.mjs
import expressiveCode from 'astro-expressive-code';
export default defineConfig({
integrations: [
expressiveCode({
styleOverrides: {
frames: {
frameBoxShadowCssValue: 'none',
},
},
}),
],
});

追加プラグイン

plugin-line-numbersplugin-collapsible-sectionsを導入しています。

Terminal window
npm i @expressive-code/plugin-line-numbers @expressive-code/plugin-collapsible-sections

下記のようにプラグインを追記します。plugin-line-numbersを導入するとデフォルトで行番号表示が有効になりますが、指定があるときのみの表示に変更しています。

astro.config.mjs
import { pluginCollapsibleSections } from '@expressive-code/plugin-collapsible-sections';
import { pluginLineNumbers } from '@expressive-code/plugin-line-numbers';
export default defineConfig({
integrations: [
expressiveCode({
plugins: [pluginCollapsibleSections(), pluginLineNumbers()],
defaultProps: {
showLineNumbers: false,
},
}),
],
});

@playform/compress

様々なファイル圧縮を実現してくれるインテグレーションです。画像/CSS/JS/HTMLファイルに有効です。

Astro標準のコンプレッション

元からAstroでは簡単なHTMLコンプレッションのオプションcompressHTMLが有効化されています。

デフォルトでは、Astroは.astroコンポーネントのHTMLから改行を含む空白を削除します。

docs.astro.buildファビコン 設定方法
docs.astro.build

現状このインテグレーションとの衝突はありませんが、デフォルトでtrueになっていることを念頭に置いておきましょう。

インストールは下記の通りです。

Terminal window
npx astro add @playform/compress

@playform/compressのHTML圧縮を有効にしているとReactコンポーネントでハイドレーションのエラー(#418)が出たので、falseにしています。

DeepL翻訳

サーバーがレンダリングした HTML がクライアントと一致しなかったため、ハイドレイティングに失敗しました。その結果、このツリーはクライアントで再生成されます。

原文

Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client.

ja.react.devファビコン Minified React error #418 – React
ja.react.dev

今のところHTML圧縮については、AstroデフォルトのcompressHTMLオプションのみで対応しています。

astro.config.mjs
import playformCompress from '@playform/compress';
export default defineConfig({
integrations: [
playformCompress({
HTML: false,
}),
],
});

npm run buildでビルドすると、圧縮結果がログに表示されます。

remarkプラグイン

astro.conig.mjsmarkdownプロパティremarkPluginsに追記することで、remarkプラグインを追加できます。

MDXインテグレーションを導入していれば、MDXファイルにも適用されます。

remark-emoji

:emoji:等で入力したパターンをUTF-8の絵文字コードに変換してくれるremarkプラグインです。

astro.config.mjs
import emoji from 'remark-emoji';
export default defineConfig({
markdown: {
remarkPlugins: [emoji],
},
});

自動的にaria-label属性も付与したり絵文字直後にスペースを加えるオプションがあったりします。詳細は公式リポジトリを参照してください。

VS Codeの絵文字入力支援

:emojisense:という拡張機能を入れています。

:から始まる文字列を入力すると、絵文字の候補が表示されるようになります。また、Ctrl+Iで絵文字の候補を検索できます。 デフォルトだとUTF-8の絵文字コードがそのまま入力されるので、その使い方であればremark-emojiプラグインは必要ありません。

UTF-8の絵文字コードではなく:some:の形式を維持したい場合には、下記の設定を有効化すればOKです。

.vscode/settings.json
{
"emojisense.unicodeCompletionsEnabled": false
}

remark-callout

GitHubのMarkdown風に> [!note]等でコールアウトブロックを記述できるようにするremarkプラグインです。

astro.config.mjs
import remarkCallout from '@r4ai/remark-callout';
export default defineConfig({
markdown: {
remarkPlugins: [remarkCallout],
},
});

プラグイン自体はdata-calloutdata-callout-title属性を付けた<div>要素をつけるだけなので、別途CSSでスタイリングが必要です。 README.mdに記載がある通り、スタイル例を共有してくれているので、今のところそれをそのまま使っています。

コールアウトの宣言後に-+をつけることで、<details><summary>の構成にして折りたたむことも可能です。

折りたたみ例

こんな感じで折り畳める

remark-heading-shift

Markdownの見出しを右シフトさせるremarkプラグインです。

Content Layer(Collections)で記事を管理している場合、フロントマター部で記事タイトルを指定することが多いです。記事を表示するページでは、記事タイトルにだけ<h1>タグを付け、それ以降は<h2>以下にしたいところです。 複数の<h1>タグを単一ページに含めることが推奨されていないのも理由の1つです。

1 つのページに複数の <h1> 要素を使用することは HTML 標準では認められていますが(入れ子でない限り)、これはよい習慣とはみなされません。1 つのページには、ページの内容を説明する 1 つの <h1> 要素(文書の <title> 要素と同様)を置くのが一般的です。

developer.mozilla.orgファビコン <h1>–<h6>: HTML の見出し要素 - HTML: ハイパーテキストマークアップ言語 | MDN
developer.mozilla.org

ところが、Markdownの見出し#<h1>タグで始まります。記事本文内で##から始めて書くのも違和感があるので、見出しを右シフトさせて<h2>タグから始まるようにしています。

astro.config.mjs
import remarkHeadingShift from 'remark-heading-shift';
export default defineConfig({
markdown: {
remarkPlugins: [remarkHeadingShift],
},
});

リンクカード

これは自作しています。前後の行が空行でかつURLべた貼りの行に対して、特定のAstroコンポーネントを割り当てることで実現しています。

具体的には、mdast(Markdown Abstract Syntax Tree)で先の条件を満たすlinkノードのhNamelink-card等の任意のタグ名に変更しています。続いて、MDXインテグレーションの下記記述で、そのタグ名とリンクカード用のAstroコンポーネントを紐づけています。 (機会があればremarkプラグインの作り方についても記事にしたい)

---
import LinkCard from '@mdx-components/LinkCard.astro';
const { Content, headings } = await render(post);
---
<Content components={{ 'link-card': LinkCard }} />

キャッシュの取り方は下記記事を参考に、urlをキーとした連想配列をjsonで持っています。

OGP情報の取得はopen-graph-scraperを使っています。

rehypeプラグイン

astro.conig.mjsのmarkdownプロパティrehypePluginsに追記することで、rehypeプラグインを追加できます。

MDXインテグレーションを導入していれば、MDXファイルにも適用されます。

外部リンクの<a>タグにtarget="_blank"rel="nofollow"等を付与するrehypeプラグインです。

Terminal window
npm install rehype-external-links

relを指定しない場合は、nofollowrelにデフォルトで指定されるようです。わざわざnofollowをつける必要があるタイミングは今のところないので、空配列にして何も付けないことを強制しています。

astro.config.mjs
import rehypeExternalLinks from 'rehype-external-links';
export default defineConfig({
markdown: {
rehypePlugins: [
[
rehypeExternalLinks,
{
target: '_blank',
rel: [],
},
],
],
},
});

Astroコンポーネント

astro-icon

iconifyのアイコンをコンポーネントから簡単に呼び出せるようになります。

Terminal window
npm i -D astro-icon

使いたいアイコンセットはそれぞれインストール必要があります。私の場合はmdi(Material Design Icons)とdeviconをインストールしています。

Terminal window
npm i -D @iconify-json/mdi @iconify-json/devicon

あとはIconコンポーネントのnameにアイコン名を指定するだけです。

---
import { Icon } from 'astro-icon/components';
---
<Icon name='mdi:home' />
<Icon name='devicon:android' />

astro-seo

<head>のSEO関連タグ(OGPやtwitterカード含む)をまとめて定義できるAstro用のコンポーネントです。

Terminal window
npm i astro-seo

このブログの場合は、共通ヘッダコンポーネントへ記事データを渡せるようにしており、その内部で下記のような実装を施しています。 astro-seoの仕様として、titleundefinedではない場合はtitleTemplate%s部にtitleが入ります。undefinedな場合はtitleDefaultの値になります。 siteConfigは自前で用意している設定ファイルです。

---
import { SEO } from 'astro-seo';
---
<SEO
34 collapsed lines
charset='utf-8'
title={post?.data.title ?? title}
titleDefault={siteConfig.title}
titleTemplate={`%s - ${siteConfig.title}`}
description={post?.data.description ?? siteConfig.description}
openGraph={{
basic: {
title: post?.data.title ?? title ?? siteConfig.title,
type: post ? 'article' : 'website',
// OG画像のフォールバック設定
image: handleOgImageFallback(),
},
optional: {
siteName: siteConfig.title,
locale: siteConfig.ogLocale,
description: post?.data.description ?? siteConfig.description,
},
// postが存在しない場合、article情報は無し
article: post
? {
publishedTime: post?.data.pubDate.toISOString(),
modifiedTime: post?.data.updatedDate?.toISOString(),
authors: [siteConfig.author],
}
: undefined,
}}
twitter={{
creator: siteConfig.xUsername,
card: 'summary_large_image',
title: post?.data.title ?? title ?? siteConfig.title,
description: post?.data.description ?? siteConfig.description,
image: handleOgImageFallback(),
imageAlt: post?.data.title ?? siteConfig.title,
}}
/>

その他ライブラリ

OGP画像の自動生成

VercelのsatoriによってHTML+CSS表現からSVGを生成し、sharpによってwebp形式に変換しています。 日本語が不自然な箇所でwrapしてしまう問題はGoogleのBudouXによる日本語の分かち書きを使って対応しています。 完全にローカルで処理でき、かつBudouXが非常に軽量なのでそこまでビルド時間もかかりません。ある程度の頻度で生成するようにキャッシュを設けてもいいかもです。

こちらの記事が大変参考になります。

RSS

@astrojs/rssパッケージと静的エンドポイントを使ってRSSフィードを生成しています。

Terminal window
npm install @astrojs/rss

src/pages/rss.xml.ts(静的エンドポイント)を作成し、下記のように実装しています。siteConfigに関しては自前で用意している設定ファイルなので適宜読み替えを。

src/pages/rss.xml.ts
import rss from '@astrojs/rss';
import siteConfig from '@config/base';
import { getCollection } from 'astro:content';
import type { APIContext } from 'astro';
export async function GET(context: APIContext) {
const blog = await getCollection('blog');
return rss({
title: siteConfig.title,
description: siteConfig.description,
site: context.site!.toString(),
items: blog.map((post) => ({
title: post.data.title,
pubDate: post.data.pubDate,
description: post.data.description,
link: `/posts/${post.id}/`,
})),
});
}

これで、/rss.xmlにアクセスするとRSSフィードが取得できるようになります。 記事の全文を含めたRSSを作りたい場合については、公式のドキュメントに案内があるので参照してください。

最後に、共通<head>に下記を追記しています。

<head>
<link
rel="alternate"
type="application/rss+xml"
title="サイト名"
href="/rss.xml"
/>
</head>

tocbot

記事内の見出しを抽出して目次を生成するライブラリです。本ブログでも、広めの画面で表示した際に右サイドバーで表示されるようにしています。

Terminal window
npm install --save tocbot

記事を表示しているレイアウトコンポーネントに下記を追記しています。 ViewTransitionまたはClientRouterでSPAの指定をしている場合は、astro:after-swapイベントを使って再描画させる必要があります。

<script>
import * as tocbot from 'tocbot';
const initializeTocbot = () => {
// Initialize tocbot
tocbot.init({
// 目次を表示先
tocSelector: '.js-toc',
// 見出しの抽出元
contentSelector: '.js-toc-content',
// 固定ヘッダ分のOffset
headingsOffset: 90,
// 抽出する見出しのレベル
headingSelector: 'h2, h3, h4',
// 全体にCSSスムーススクロールを適用しているためfalse
scrollSmooth: false,
// 対象の位置にいない場合に、is-collapsedクラスを自動付与する深さ
collapseDepth: 2,
});
};
initializeTocbot();
// Astroのページ遷移後に目次を再描画
document.addEventListener('astro:after-swap', initializeTocbot);
</script>

関連記事