Astroの便利なプラグインやライブラリ
目次
はじめに
本ブログはAstroで構築されています。 実装するにあたって有用だったライブラリやインテグレーションを紹介します。
インテグレーション
Tailwind
公式のインテグレーションをそのまま使って導入しています。
TailwindもNestingでCSSに記述できるように、下記オプションを追記。
export default defineConfig({ integrations: [ tailwind({ nesting: true, }), ],});
typography
prose
クラスを割り当てた要素に対して、記事っぽいスタイルを適用してくれるTailwindプラグインです。
見出し要素(h1
~h6
)や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()関数
下記記事を参考に、clsx
とtailwind-merge
モジュールを使ったヘルパー関数を定義しています。
クラス名の振り分けが簡単になり、コードも読みやすくなります。
MDX
公式のインテグレーションをそのまま使って導入しています。
Sitemap
公式のインテグレーションで導入しています。
ドキュメント通りintegrations
に追記すれば導入完了です。
あとは、共通<head>
への追記とrobots.txt
を設置すればOKです。
<head> <link rel="sitemap" href="/sitemap-index.xml" /></head>
robots.txt
にドメインをハードコーディングしたくない場合は、静的エンドポイントとして実装できます。
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.mjs
のsite
プロパティが入ってくるので、ドメインが変わってもrobots.txt
の内容を変更する必要がなくなります。
Expressive Code
コードブロックのシンタックスハイライタです。diff表現や行コメント、ターミナルのフレーム等が標準で備わっていているのでこちらを採用。
インストールのドキュメントにある通り、Astro用のインテグレーションも用意されています。
npx astro add astro-expressive-code
MDXインテグレーションを使っている場合には、mdx()
よりも前にexpressiveCode()
を追記する必要があります。
テーマ一覧
スタイル調整
styleOverrides
プロパティで上書きできます。ドキュメントから該当するパラメータを探して設定してください。
例えば、フレームの影を消したい場合は下記のように設定します。
import expressiveCode from 'astro-expressive-code';export default defineConfig({ integrations: [ expressiveCode({ styleOverrides: { frames: { frameBoxShadowCssValue: 'none', }, }, }), ],});
追加プラグイン
plugin-line-numbersとplugin-collapsible-sectionsを導入しています。
npm i @expressive-code/plugin-line-numbers @expressive-code/plugin-collapsible-sections
下記のようにプラグインを追記します。plugin-line-numbersを導入するとデフォルトで行番号表示が有効になりますが、指定があるときのみの表示に変更しています。
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では簡単なHTMLコンプレッションのオプションcompressHTML
が有効化されています。
デフォルトでは、Astroは.astroコンポーネントのHTMLから改行を含む空白を削除します。
設定方法
docs.astro.build
現状このインテグレーションとの衝突はありませんが、デフォルトでtrue
になっていることを念頭に置いておきましょう。
インストールは下記の通りです。
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.
Minified React error #418 – React
ja.react.dev
今のところHTML圧縮については、AstroデフォルトのcompressHTML
オプションのみで対応しています。
import playformCompress from '@playform/compress';export default defineConfig({ integrations: [ playformCompress({ HTML: false, }), ],});
npm run build
でビルドすると、圧縮結果がログに表示されます。
remarkプラグイン
astro.conig.mjs
のmarkdown
プロパティremarkPlugins
に追記することで、remarkプラグインを追加できます。
MDXインテグレーションを導入していれば、MDXファイルにも適用されます。
remark-emoji
:emoji:
等で入力したパターンをUTF-8の絵文字コードに変換してくれるremarkプラグインです。
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です。
{ "emojisense.unicodeCompletionsEnabled": false}
remark-callout
GitHubのMarkdown風に> [!note]
等でコールアウトブロックを記述できるようにするremarkプラグインです。
import remarkCallout from '@r4ai/remark-callout';
export default defineConfig({ markdown: { remarkPlugins: [remarkCallout], },});
プラグイン自体はdata-callout
やdata-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> 要素と同様)を置くのが一般的です。
<h1>–<h6>: HTML の見出し要素 - HTML: ハイパーテキストマークアップ言語 | MDN
developer.mozilla.org
ところが、Markdownの見出し#
は<h1>
タグで始まります。記事本文内で##
から始めて書くのも違和感があるので、見出しを右シフトさせて<h2>
タグから始まるようにしています。
import remarkHeadingShift from 'remark-heading-shift';
export default defineConfig({ markdown: { remarkPlugins: [remarkHeadingShift], },});
リンクカード
これは自作しています。前後の行が空行でかつURLべた貼りの行に対して、特定のAstroコンポーネントを割り当てることで実現しています。
具体的には、mdast(Markdown Abstract Syntax Tree)で先の条件を満たすlinkノードのhName
をlink-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ファイルにも適用されます。
rehype-external-links
外部リンクの<a>
タグにtarget="_blank"
やrel="nofollow"
等を付与するrehypeプラグインです。
npm install rehype-external-links
rel
を指定しない場合は、nofollow
がrel
にデフォルトで指定されるようです。わざわざnofollow
をつける必要があるタイミングは今のところないので、空配列にして何も付けないことを強制しています。
import rehypeExternalLinks from 'rehype-external-links';export default defineConfig({ markdown: { rehypePlugins: [ [ rehypeExternalLinks, { target: '_blank', rel: [], }, ], ], },});
Astroコンポーネント
astro-icon
iconifyのアイコンをコンポーネントから簡単に呼び出せるようになります。
npm i -D astro-icon
使いたいアイコンセットはそれぞれインストール必要があります。私の場合はmdi(Material Design Icons)とdeviconをインストールしています。
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用のコンポーネントです。
npm i astro-seo
このブログの場合は、共通ヘッダコンポーネントへ記事データを渡せるようにしており、その内部で下記のような実装を施しています。
astro-seoの仕様として、title
がundefined
ではない場合はtitleTemplate
の%s
部にtitle
が入ります。undefined
な場合はtitleDefault
の値になります。
siteConfig
は自前で用意している設定ファイルです。
---import { SEO } from 'astro-seo';---
<SEO34 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フィードを生成しています。
npm install @astrojs/rss
src/pages/rss.xml.ts
(静的エンドポイント)を作成し、下記のように実装しています。siteConfig
に関しては自前で用意している設定ファイルなので適宜読み替えを。
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
記事内の見出しを抽出して目次を生成するライブラリです。本ブログでも、広めの画面で表示した際に右サイドバーで表示されるようにしています。
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>
関連記事

