ブログをAstroに移行しました
ブログをAstroに移行しました Photo by   NASA   on   Unsplash
目次

今回、個人ブログを Next.js から Astro に移行しました。

それに合わせてスタイリングも Chakra UI から ずっと触ってみたかった Panda CSS に変更しました。

なぜ Astro に移行したのか

以前から Astro を触りたいな―と思っていたり、Twitter1 などで Astro はいいぞみたいなことを見かけていたので、
今回ブログを移行するにあたって Astro にしてみました。

あと、Deno の Web フレームワークの Fresh を使って 個人サイト を作ったときに Astro の Islands Architecture 2 を知ったのも理由の一つです。

Astro に移行する

Astro の公式サイトを参考に Next.js から Astro に移行しました。
Next.js との違いとかはドキュメントで詳しく解説されてます。

書き味は Next.js とほぼ同じなので、移行はそこまで難しくなかったです。

MDX でブログを入稿する (Content Collections)

ブログを移行するにあたって、今まで microCMS で管理していた記事を MDX で管理・入稿できるようにしました。

今回の移行にあたって、リッチエディタで記事を書くよりも、MDX で記事を書いたほうが良いと思ったからです。

また、自分ひとりで管理するので microCMS のようなオンラインのヘッドレス CMS よりも MDX で管理したほうが多分楽です。

Content Collections

Astro では Content Collections という機能を使うことで、簡単かつ型安全に MDX で記事を管理できます。

Astro ではsrc/contentが予約ディレクトリとなり、直下にあるディレクトリがコレクションとして認識されます。

例えばこんな感じにすると、blogというコレクションが作成されます。

src/content
├── blog
│   ├── article-1.mdx
│   ├── article-2.mdx
│   ├── article-3.mdx
└── config.ts

これでsrc/content/config.tsに以下のように設定すると、blogコレクションの記事を MDX で管理できるようになります。

import { defineCollection } from "astro:content";
const blogCollection = defineCollection({
  /* ... */
});
export const collections = {
  blog: blogCollection, // content直下のフォルダ名と一致させる
};

そして、src/pages[...slug].astroファイルを作成すると、src/content/blog以下の記事を全てページとして生成してくれます。

---
import type { CollectionEntry } from "astro:content";
import { getBlogEntries } from "../utils/blog";
import BlogPost from "../layouts/BlogPost.astro";

// すべての記事を取得して、ページを生成する
export async function getStaticPaths() {
  const posts = await getBlogEntries();

  return posts.map((post) => ({
    params: {slug: post.slug},
    props: post
  }))
}

type Props = CollectionEntry<"blog">;
const post = Astro.props
const { Content } = await post.render()
---

<BlogPost {...post.data}>
  <Content />
</BlogPost>

Frontmatter のバリデーション

また、zod を使ったFrontmatterのバリデーションもできます。

例えばこんな感じの Frontmatter だとする

---
slug: blog-renewal-to-astro
title: ブログをAstroに移行しました
description: ブログをAstroに移行したので、そのメモです。
tags: ["Typescript", "Astro"]
publishDate: 2023-08-29
---

この場合src/content/config.tsで以下のようにバリデーションできる

import { defineCollection, z } from "astro:content";

const blogCollection = defineCollection({
  type: "content",
  schema: ({ image }) =>
    z.object({
      title: z.string(),
      tags: z.array(z.string()),
      description: z.string(),
      publishDate: z.date(),
    }),
});

export const collections = {
  blog: blogCollection,
} as const;

Panda CSS に変更した

以前のブログはスタイリングに Chakra UI を使っていましたが、今回は Panda CSS を使ってみました。

Panda CSS は Chakra UI が開発した Zero Runtime な CSS-in-JS です。

Design Tokens 3 などもサポートしているので、かなり開発体験が良かったです。

Panda CSS でダークモード

panda.config.tsに以下のように設定するだけで、Panda CSS でも簡単にダークモードを実装できます。

import { defineConfig } from "@pandacss/dev";

export default defineConfig({
  // ...
  conditions: {
    dark: ".dark &",
  },
  //...
});

これでスタイリングのときに_dark: {color: "white"}のように書くだけで、ダークモードに対応できます。

import { css } from "{project-root}/styled-system/css";

// ダークモードのときは白色にする
<h1
  class={css({
    color: {
      base: "#000",
      _dark: "#FFF",
    },
  })}
>
  Hello World
</h1>;

普通はSemantic Tokensを使ってダークモード時の色を定義するのが良さそう

// panda.config.ts
import { defineConfig } from "@pandacss/dev";

export default defineConfig({
  // ...
  semanticTokens: {
    colors: {
      text: {
        base: "#000",
        _dark: "#FFF",
      },
    },
  },
  //...
});

こうすると先程の例がよりシンプルに書ける

import { css } from "{project-root}/styled-system/css";

<h1 class={css({ colors: "text" })}>Hello World</h1>;

まとめ

Next.js+Chakra UI で作ったブログを Astro+Panda CSS に移行した話をしました。

Astro はデフォルトで JS 配信が無いサイトを作ることができるので、ブログのような Content First なサイトを作るのに向いていると思います。

あと、React や Preact, Svelte などの自分の好きな UI フレームワークを使うことができるのもとても良いです。

Footnotes

  1. 今は X

  2. Jason Miller 氏の記事は こちら

  3. W3C の Design Tokens Format