個人サイトをAstroJSで書き直した
個人サイトをAstroJSで作り直した
またやった。
コンテンツ本体の管理は組み込みのastro:contentを使っている。
CSS
Astroはデータ属性とセレクタを組み合わせることで.astroファイル内にCSSの適用領域を限定している:hoverの扱いに苦労するだとかをしなくて良い。
ただしMarkdownだけはこの仕組みに乗っかることは難しいのでglobal.cssでスタイルを書いている。
デザインはreset.cssを適用してから自分で手で書いた。
CSS変数を定義して0.5 rem単位で調整してある程度一貫性を持たせるように努めたがすでに破綻しつつある。
Markdownのレンダリング
reamrk-sectionizeだけ導入した。タグがあるので対応した。astro.config.jsを弄るだけで済む。
import react from "@astrojs/react";
import sitemap from "@astrojs/sitemap";
import icon from "astro-icon";
import { defineConfig } from "astro/config";
import remarkSectionize from "remark-sectionize";
// https://astro.build/config
export default defineConfig({
site: "https://www.namachan10777.dev",
integrations: [icon(), react(), sitemap()],
markdown: {
remarkPlugins: [remarkSectionize],
},
});
ブログページのプレビューテキストはreamrkでHTML化した文字列をjsdomでパースして.window.document.body.textContetを取得する力技で実装した。これより効率的な実装はあり得るが、どうせビルド時に行われることなので多分この実装が最適。
OG画像
satoriとReactで生成した画像をsharpでWebPに変換している。getStaticPathsをexportすればファイルをビルド時に生成出来るので便利だ。package.jsonには含まれるが、ページに出力されるコンポーネントのロジックは全てVanilla JS(TS)で書いているので
エンドポイントのコードは下記のようになる。ogImageはsatoriを使ってOG画像を生成する関数だ。satoriにReactのJSXとフォントデータを与えればSVGが文字列として出てくるのでsharp(Buffer.from(svg)).webp().toBuffer()とするだけ。
export const GET: APIRoute = async ({ params }) => {
const article = await getEntryBySlug("blog", params.slug as any);
const title = article?.data.title;
const description = article?.data.description;
const body = await ogImage({
title: title || "No title",
titleFontSize: 4,
description,
url: `https://namachan10777.dev/blog/`,
width,
height,
});
return new Response(body);
};
RSS
@astrojs/rssを使った。astro:contentのAPIでfrontmatterを取ってきてGETエンドポイントの形で書くだけ。
import rss from "@astrojs/rss";
import type { APIRoute } from "astro";
import { getCollection } from "astro:content";
const blog = await getCollection("blog");
export const GET: APIRoute = async (ctx) => {
return rss({
title: "namachan10777 Blog",
description: "分散システム、ストレージ、Web、あとそのほか",
site: ctx.site || "https://www.namachan10777.dev",
items: blog
.sort((a, b) => a.data.date.getTime() - b.data.date.getTime())
.map((blog) => ({
title: blog.data.title,
pubDate: blog.data.date,
description: blog.data.description,
link: `/blog/`,
})),
});
};
動的なページ変化
Reactは使っていない。 WebアプリケーションでもないのにReactを使うのはランタイムがデカすぎる。タグでshadow rootを宣言出来る機能)に対応していなかったのでShadow rootは使っていないし、大体SSGフレームワークを使っている以上カスタムタグを使える嬉しさもあまりない。document.querySelectorするのでも別に良かった。
展望
Pagefindを使って検索機能を実装したいが、かなり扱いが難しい。
ページネーションも実装したい。記事が少ないうちに考えるようなことではない気がするが。