EY-Office ブログ

ブログのようなWebサイトを構築するにはAstroが良いかも

最近 AstroというWebサイト構築ツールが話題になっているようなので試してみました。

Astroはブログや企業のホームページを作ったりするためのツールです。この分野ではWordPressが有名ですが、性能やセキュリティーの面からGatsbyHugoなどのSSG (Static Site Generators)も使われるようになって来ています、Astroも同じくSSGツールです。

Astro

Astroとは

Astroのサイトには日本語のドキュメントもありますよ😁

Astroを選ぶ理由には以下のように書かれています。

  • コンテンツ重視: Astroは、コンテンツが豊富なWebサイトのために設計されています。
  • サーバーファースト: HTMLをサーバーでレンダリングすることで、Webサイトの動作が速くなります。
  • デフォルトで高速: Astroで遅いウェブサイトを構築することは不可能です。
  • 簡単に使える: 専門家でなくても、Astroで何かを構築できます。
  • 充実した機能と柔軟性: 100以上のAstroインテグレーションから選択できます。

興味を持った方は、Astroを選ぶ理由ページを読んで下さい。
さらに主な特長には以下のような技術的な特徴が書かれています。

  • コンポーネントアイランド: 高速なウェブサイトを構築するための新しいウェブアーキテクチャー。
  • サーバーファーストのAPI設計: ユーザーのデバイスから高コストのハイドレーションをなくします。
  • デフォルトでゼロJS: サイトを遅くするJavaScriptランタイムオーバーヘッドはありません。
  • エッジ対応: DenoやCloudflareのようなグローバルなエッジを含め、どこでもデプロイできます。
  • カスタマイズ可能: Tailwind、MDX、その他100以上のインテグレーションから選択可能です。
  • 特定のUIに依存しない: React、Preact、Svelte、Vue、Solid、Litなどをサポートします。

ざっくり言うと、WordPressにかわるナウなWebサイト構築ツールです。

EY-Officeサイト

さて、このブログを含むEY-OfficeのホームページJekyllというRubyで書かれたSSG(Static Site Generators)を使っています。

しかしサイトの更新速度が遅いなどの問題もあり、JavaScriptベースのSSGに乗り換えようと試みて来ましたEY-OfficeサイトもJamstackブームに乗ってみようと思う(1)(2)(3)(4) に書いたようにNext.jsのSSGに乗り換えようとしたのですが頓挫してしまいました。

ある程度Next.js版が動くようになってみると、ページの生成が遅いのです。その後のNext.jsのバージョンアップで差分更新が実装されたらしいですが・・・やる気が無くなっていました😅

EY-Officeブログを考慮したブログサイトをAstroで作ってみた

AstroでEY-Officeブログ(サイト)を作り直す場合に考慮しなくてはいけないのは、

  1. 生成されるサイトのURLは現在と同じ
  2. できるだけ、現在のブログ記事が使えるようにする

です、1.はマストです。

そこで、今回は以下のような現状と同じファイル構造のブログ記事から

└── src
    └── pages
        └── _blog
            ├── 2022-12-01-thought-about-reskilling.md
            ├── 2022-12-06-what-about-my-next-development-mac.md
            └── 2022-12-13-wonders-of-the-chai-assertion-framework.md

現状と同じURLのサイトが構築される事を目標に作りました

  • http://localhost:3000/blog/2022/12/01/thought-about-reskilling 最初のブログ記事
  • http://localhost:3000/blog/2022/12/06/what-about-my-next-development-mac 2番目のブログ記事
  • http://localhost:3000/blog/2022/12/13/wonders-of-the-chai-assertion-framework 3番目のブログ記事

コードの解説

今回作ったブログ・サイトのコードはGitHubに置きましたが、Astro説明もかねて一部コードの説明してみます。

├── README.md
├── astro.config.mjs        ← ①
├── package-lock.json
├── package.json            ← ②
├── public                  ← ③
│   ├── favicon.svg
│   └── images
│       ├── Masala-Chai.jpg
│       ├── pcs.png
│       └── reskilling.png
├── src
│   ├── components          ← ④
│   │   └── Card.astro
│   ├── env.d.ts
│   ├── layouts             ← ⑤
│   │   └── Layout.astro
│   ├── pages               ← ⑥
│   │   ├── _blog           ← ⑦
│   │   │   ├── 2022-12-01-thought-about-reskilling.md
│   │   │   ├── 2022-12-06-what-about-my-next-development-mac.md
│   │   │   └── 2022-12-13-wonders-of-the-chai-assertion-framework.md
│   │   ├── blog
│   │   │   ├── [y]
│   │   │   │   └── [m]
│   │   │   │       └── [d]
│   │   │   │           └── [name].astro  ← ⑧
│   │   │   └── index.astro               ← ⑨
│   │   └── index.astro                   ← ⑩
│   └── utils
│       └── BlogPath.ts                   ← ⑪
└── tsconfig.json                         ← ⑫

Astroの基本

⑩ .astroファイル src/pages/index.astro

.astroファイルはAstroのコンテンツやコンポーネント等を作る、とっとも基本的なファイルで以下のように---に挟まれた上部はコンポーネントスクリプトと呼ばれJavaScriptのコードが書けます。

  • この例ではLayout、Cardと呼ばれるコンポーネントをimport文で取り込んでいます。

---の下の部分はコンポーネントテンプレートと呼ばれHTMLとCSSが書かれます。

  • Vue.jsのように、HTMLと、このHTML使うCSSが<style>内に書けます
    • HTMLタグのclass名は1つの.astroファイル内に限定されるので便利
  • ReactやVue.jsと同じく大文字から始まるタグはコンポーネントです。コンポーネントには属性を通じてプロパティー(引数)を渡せます
---
import Layout from '../layouts/Layout.astro';
import Card from '../components/Card.astro';
---

<Layout title="Welcome to Astro.">
  <main>
    <h1>Welcome to <span class="text-gradient">Astro</span></h1>
    <ul role="list" class="link-card-grid">
      <Card
        href="/blog"
        title="ブログ"
        body="将来のEY-Officeブログに向けてのスタート"
      />
    </ul>
  </main>
</Layout>

<style>
  main {
    margin: auto;
    padding: 1.5rem;
    max-width: 60ch;
  }

・・・省略・・・

</style>
④ コンポーネント src/components/Card.astro

コンポーネントのコンポーネントスクリプトには、プロパティーの宣言とAstro.propsでプロパティーを取出すコードが書かれています。

コンポーネントテンプレート内の{・・・}は、React同様にJavaScriptの値が取得でき、生成されるHTMLに埋め込まれます。

---
export interface Props {
  title: string;
  body: string;
  href: string;
}

const { href, title, body } = Astro.props;
---

<li class="link-card">
  <a href={href}>
    <h2>
      {title}
      <span>&rarr;</span>
    </h2>
    <p>
      {body}
    </p>
  </a>
</li>

<style>

・・・省略・・・

</style>
⑤ レイアウト src/layouts/Layout.astro

レイアウトもコンポーネントです。<slot />タグのところにコンテンツが入ります

---
export interface Props {
  title: string;
}

const { title } = Astro.props;
---

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width" />
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <meta name="generator" content={Astro.generator} />
    <title>{title}</title>
  </head>
  <body style="width: 80%; margin: 0 auto 0 auto;">
    <slot />
  </body>
</html>

<style is:global>

・・・省略・・・

</style>
⑦ Markdwon src/pages/_blog/2022-12-01-thought-about-reskilling.md

コンテンツには標準でMarkdownも使えます。コンポーネントスクリプトにはレイアウトの指定や、レイアウト.astro内で参照できるfrontmatterオブジェクトのプロパティーを設定できます。

---
title: リスキリングに付いて考えてみた
---

最近**リスキリング**という言葉をよく聞くようになってきましたね。

🐿️リス・Killingじゃないですよ、Reskilling (Re・skill・ing)です。
なぜ日本でこの言葉が使われだしたのでしょうか?
[リスキリングとは - 経済産業省(PDF)](https://www.meti.go.jp/shingikai/mono・・・.pdf)
このへんが元なのでしょうか ???

・・・省略・・・
③ public/

public/ディレクトリー下には、HTMLから参照される画像などを置きます。

Astroの機能

⑥ pages

src/pages/ 以下に置かれた.astro.mdコンテンツはそのまま公開される.htmlに変換されます。また、フォルダーがあれば/フォルダー名/のようにURLに対応します。

ただし、src/pages/_blog/ のようにアンダーラインから始まるディレクトリーは公開(変換)されません。

⑧ ダイナック・ルーティング src/pages/blog/[y]/[m]/[d]/[name].astro

フォルダーやファイル名に[・・・](ブラケット)が含まれる場合は.astroファイル内で指定されたパスに変換されます。ここでは[y]/[m]/[d]/[name]2022/12/01/thought-about-reskillingのように変換されます。

  • getStaticPaths()関数はパス名やプロパティーを戻す関数です、ここに戻すparamsの値がブラケットに対応します
    • Astro.glob('/src/pages/_blog/*.md')/src/pages/_blog/ディレクトリーにある全.mdファイルのコンテンツの配列を戻します
    • blogParms()関数はコンテンツのファイル名が対応するy, m, d, nameの値を戻します(詳細は後で説明します)。
    • プロパティーpropsにはHTMLに変換されたコンテンツとタイトルを戻します
  • getStaticPaths()関数の戻り値に対応したコンポーネントテンプレートを使いコンテンツが作成されます
    • Astro.paramsAstro.propsgetStaticPaths()関数が戻した値を取得
    • レイアウトは<Layout>を利用
    • <p>でブログの日付を表示
    • <h1>でブログのタイトルを表示
    • <div>内にブログのコンテンツが表示
---
import Layout from '@layouts/Layout.astro';
import { blogParms } from '@utils/BlogPath';

export async function getStaticPaths() {
  const posts = await Astro.glob('/src/pages/_blog/*.md');

  const paths = posts.map(post => {
    return {
      params: blogParms(post),
      props: { post: post.compiledContent(), title: post.frontmatter.title}
    };
  })

  return paths;
}


const { y, m, d } = Astro.params;
const { post, title } = Astro.props;
---

<Layout title={title}>
  <p style="color: gray">{y}-{m}-{d}</p>
  <h1>{title}</h1>
  <hr/>
  <div set:html={post} />
</Layout>
⑪ JavaScriptのコード src/utils/BlogPath.ts

ページ生成に関わるJavaScript(TypeScript)関数を定義できます。

  • blogParms関数はコンテンツのファイル名からy, m, d, nameを戻します
    • 2022-12-01-thought-about-reskilling.md なら {y: '2022', m: '12', d: '01', name: 'thought-about-reskilling'}が戻ります
  • blogUrl関数はコンテンツに対応するURLを戻します
    • 2022-12-01-thought-about-reskilling.md なら /blog/2022/12/01/thought-about-reskillingが戻ります
import type { MarkdownInstance } from "astro";

export const blogParms = (post: MarkdownInstance<Record<string, any>>) => {
  const [_, y, m, d, name] = post.file.match(/_blog\/(\d+)-(\d+)-(\d+)-(.+?).md/) ?? [0, 1,1,1,''];
  return {y, m, d, name};
}

export const blogUrl = (post: MarkdownInstance<Record<string, any>>) => {
  const p = blogParms(post);
  return `/blog/${p.y}/${p.m}/${p.d}/${p.name}`;
};
⑨ 動的なコンテンツ src/pages/blog/index.astro

以下はブログ一覧ページの.astroファイルです。

ここではposts配列の内容から<li> 2022-12-01 : <a href="/blog/2022/12/01/thought-about-reskilling"> リスキリングに付いて考えてみた </a></li>のような行を作成しています。コンテンツを動的に作る方法はReactのJSXに似ています。

---
import Layout from '@layouts/Layout.astro';
import { blogUrl, blogParms } from '@utils/BlogPath';

const posts = await Astro.glob('/src/pages/_blog/*.md');
---

<Layout title="Blog list">
  <ul>
    {posts.map(p => {
      const {y,m,d} = blogParms(p)
      return (<li> {`${y}-${m}-${d}`} :  <a href={blogUrl(p)}> {p.frontmatter.title} </a></li>);
    })}
  </ul>
</Layout>

設定

① astro.config.mjs

Astroの設定ファイルです。詳細は省きますが、ここではMarkdownのシンタックスハイライトの設定をしています。

import { defineConfig } from 'astro/config';

export default defineConfig({
  markdown: {
    shikiConfig: {
      theme: 'light-plus',
      langs: [],
      wrap: true,
    }
  }
});
② package.json

変更はしていませんが、以下のようなコマンドが定義されています。

  • npm start (npm run dev) 開発環境の起動
  • npm run build 公開用HTML/CSSの作成
  • npm run preview buildで作られたHTML/CSS表示用サーバーの起動
  "scripts": {
    "dev": "astro dev",
    "start": "astro dev",
    "build": "astro build",
    "preview": "astro preview",
    "astro": "astro"
  },
⑫ tsconfig.json

import fromで指定するパス内の @・・・ の定義、ここでは@utils@layouts を定義しています。

{
  "extends": "astro/tsconfigs/strict",
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@utils/*": ["src/utils/*"],
      "@layouts/*": ["src/layouts/*"]
    }
  }
}

まとめ

Astroは使いやすいWebサイト構築ツールだと思います。とくに少しでもReactやVue.jsを使った方には簡単に使えると思います。
ビルドの時間も短く、日本語ドキュメントもあり、とてもお薦めのツールだと思います。

JekyllからNext.jsのSSGに乗り換えは失敗しましたが、Astroへの乗り換えは出来そうな気がします。

- about -

EY-Office代表取締役
・プログラマー
吉田裕美の
開発者向けブログ