Astro でブログ再構築したので概要を紹介
最近少しトレンドになっている Astro を利用してブログを再構築したので、 Astro の概要について紹介します。
Astro 概要
コンテンツ重視型 SSG (Static Site Generator) です。 公式サイトでは、以下のように紹介されています。
Astro is an all-in-one web framework for building fast, content-focused websites.
類似のツールとして、 Hugo や Jekyll 等が有名ですが、以下が Astro の最大の特徴だと感じました。
特に二つ目の Zero JS, by default
は、今後他のフレームワークでも採用が進むかもしれません。
詳細は公式ページをご参照ください。
- Astro Islands: ページ内のエリアを Island として分離し、それぞれ HTML, React, Vue 等を利用可能
- Zero JS, by default: ビルドで可能な限り HTML に変換し、JavaScript を生成しないことで高速化
Play with Astro
公式から提供されているブログテンプレートを利用したセットアップを紹介します。 公式チュートリアルも提供されているので、そちらもご参考ください。
セットアップ
npm create astro@latest
を実行してください。
後はウィザードに従って入力すると、セットアップ完了です。
npm run dev
または astro dev
で、開発用サーバーを起動できます。
デフォルトは、 http://localhost:4321/
でリクエストをリスンしています。
npm create astro@latest
astro Launch sequence initiated.
dir Where should we create your new project?
./
tmpl How would you like to start your new project?
Use blog template
██████ Template copying...
deps Install dependencies?
Yes
██████ Installing dependencies with npm...
ts Do you plan to write TypeScript?
Yes
use How strict should TypeScript be?
Strict
██████ TypeScript customizing...
git Initialize a new git repository?
Yes
██████ Git initializing...
next Liftoff confirmed. Explore your project!
Run npm run dev to start the dev server. CTRL+C to stop.
Add frameworks like react or tailwind using astro add.
Stuck? Join us at https://astro.build/chat
╭─────╮ Houston:
│ ◠ ◡ ◠ Good luck out there, astronaut! 🚀
╰─────╯
ディレクトリ構成
インストール直後のディレクトリ構成です。 フロントエンド開発の経験があれば、ディレクトリ構成を見ると、それぞれの役割が分かると思います。 詳細は公式ページをご参照ください。
tree --dirsfirst -I dist -I node_modules
.
├── public
│ ├── fonts
│ │ ├── atkinson-bold.woff
│ │ └── atkinson-regular.woff
│ ├── blog-placeholder-1.jpg
│ ├── blog-placeholder-2.jpg
│ ├── blog-placeholder-3.jpg
│ ├── blog-placeholder-4.jpg
│ ├── blog-placeholder-5.jpg
│ ├── blog-placeholder-about.jpg
│ └── favicon.svg
├── src
│ ├── components
│ │ ├── BaseHead.astro
│ │ ├── Footer.astro
│ │ ├── FormattedDate.astro
│ │ ├── Header.astro
│ │ └── HeaderLink.astro
│ ├── content
│ │ ├── blog
│ │ │ ├── first-post.md
│ │ │ ├── markdown-style-guide.md
│ │ │ ├── second-post.md
│ │ │ ├── third-post.md
│ │ │ └── using-mdx.mdx
│ │ └── config.ts
│ ├── layouts
│ │ └── BlogPost.astro
│ ├── pages
│ │ ├── blog
│ │ │ ├── [...slug].astro
│ │ │ └── index.astro
│ │ ├── about.astro
│ │ ├── index.astro
│ │ └── rss.xml.js
│ ├── styles
│ │ └── global.css
│ ├── consts.ts
│ └── env.d.ts
├── README.md
├── astro.config.mjs
├── package-lock.json
├── package.json
└── tsconfig.json
Content Collections
概要
ブログ記事等のコンテンツは、コレクションとして管理します。 詳細は公式ページをご参照ください。
src/content/
の下に任意のディレクトリを作成し、そのディレクトリの中に Markdown ファイルを配置します。
MDX も利用可能です。
以下は、 blog/first-post.md
の一部です。
---
title: 'First post'
description: 'Lorem ipsum dolor sit amet'
pubDate: 'Jul 08 2022'
heroImage: '/blog-placeholder-3.jpg'
---
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
...
コレクション定義
src/content/config.ts
の中で、 Content のメタデータ (frontmatter) や型を Zod を利用して定義します。
コレクションを定義しなくても利用可能ですが、公式ページで以下のように言及されているとおり、メリットをスポイルするのでコレクションを定義することを強くお勧めします。
The
src/content/config.ts
file is optional. However, choosing not to define your collections will disable some of their best features like frontmatter schema validation or automatic TypeScript typings.
以下は、セットアップ直後の config.ts
です。
import { defineCollection, z } from 'astro:content';
const blog = defineCollection({
// Type-check frontmatter using a schema
schema: z.object({
title: z.string(),
description: z.string(),
// Transform string to Date object
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
heroImage: z.string().optional(),
}),
});
export const collections = { blog };
キー | 型 | 必須 |
---|---|---|
title | string | Y |
description | string | Y |
pubDate | Date | Y |
updatedDate | Date | N |
heroImage | string | N |
コレクション利用
getCollection
や getEntry
等の関数を利用してコレクションにアクセスできます。
Frontmatter は、 data
の中に格納されています。
例えば、以下のようなコードでアクセスできます。
---
import { getEntry } from 'astro:content';
const blogPost = await getEntry('blog', 'welcome');
---
<h1>{blogPost.data.title}</h1>
<p>{blogPost.data.description}</p>
ページ
ルーティング
ファイルベースです。
pages
の下に置いたファイルがそのままルーティング設定になります。
セットアップ直後の例だと、以下のようにアクセスできます。
ファイル | URL |
---|---|
pages/index.astro | https://<YOUR_DOMAIN>/ |
pages/about.astro | https://<YOUR_DOMAIN>/about/ |
pages/blog/index.astro | https://<YOUR_DOMAIN>/blog/ |
ダイナミックルーティングも可能です。
[...NAME]
をディレクトリ名やファイル名として利用します。
セットアップ直後の例だと、 blog/[...slug].astro
がダイナミックルーティングを利用しています。
[...slug]
の部分は、ファイルの中で getStaticPaths
関数の戻り値で定義します。
blog/[...slug].astro
の例だと、8行目で slug: post.slug
を設定しています。
---
import { type CollectionEntry, getCollection } from 'astro:content';
import BlogPost from '../../layouts/BlogPost.astro';
export async function getStaticPaths() {
const posts = await getCollection('blog');
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>
ページ構成
ページ、レイアウト、コンポーネントは、一般的なフロントエンドと同様の構成です。 もちろん、ページの中で直接コンポーネントを配置するという使い方もできます。
Astro コンポーネント
React JSX/TSX を利用したことがあれば、違和感なく扱えると思います。
以下は、 pages/index.astro
の一部です。
詳細は公式ページをご参照ください。
---
import BaseHead from "../components/BaseHead.astro";
import Header from "../components/Header.astro";
import Footer from "../components/Footer.astro";
import { SITE_TITLE, SITE_DESCRIPTION } from "../consts";
// This will cause an error.
// const main = document.querySelector('main');
---
<!doctype html>
<html lang="en">
<head>
<BaseHead title={SITE_TITLE} description={SITE_DESCRIPTION} />
</head>
<body>
<Header title={SITE_TITLE} />
<main>
<h1>🧑🚀 Hello, Astronaut!</h1>
<!-- ... -->
</main>
<Footer />
<script>
// Use document, windoww, etc. here.
const main = document.querySelector('main');
</script>
</body>
</html>
重要なポイントとして、 ---
の中は Node.js で処理されるという点です。
これは、冒頭に書いた「ビルドで可能な限り HTML に変換し、JavaScript を生成しないことで高速化」と関係があります。
したがって、 document
, window
等を利用すると以下のようなエラーになります。利用したい場合、<script>
タグの中に記述してください。
error document is not defined
Hint:
Browser APIs are not available on the server.
Move your code to a <script> tag outside of the frontmatter, so the code runs on the client.
See https://docs.astro.build/en/guides/troubleshooting/#document-or-window-is-not-defined for more information.
レンダー
Content
というコンポーネントを利用します。
CollectionEntry
から Content
を取得できます。
以下は、 pages/blog/[...slug].astro
の一部です。
15行目でレンダーした内容を取得し、19行目で Content
を任意の場所に配置しています。
---
import { type CollectionEntry, getCollection } from 'astro:content';
import BlogPost from '../../layouts/BlogPost.astro';
export async function getStaticPaths() {
const posts = await getCollection('blog');
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>
直接 Markdown を import する場合、以下のように利用できます。
---
import {Content as PromoBanner} from '../components/promoBanner.md';
---
<h2>Today's promo</h2>
<PromoBanner />
ビルド
npm run build
または astro build
を実行すると、 dist/
ディレクトリにビルドアーティファクトが出力されます。
以下は、セットアップ直後にビルドした場合のアーティファクトです。
ご覧のとおり、JavaScript が生成されていません。
tree dist/ --dirsfirst
dist/
├── about
│ └── index.html
├── blog
│ ├── first-post
│ │ └── index.html
│ ├── markdown-style-guide
│ │ └── index.html
│ ├── second-post
│ │ └── index.html
│ ├── third-post
│ │ └── index.html
│ ├── using-mdx
│ │ └── index.html
│ └── index.html
├── fonts
│ ├── atkinson-bold.woff
│ └── atkinson-regular.woff
├── blog-placeholder-1.jpg
├── blog-placeholder-2.jpg
├── blog-placeholder-3.jpg
├── blog-placeholder-4.jpg
├── blog-placeholder-5.jpg
├── blog-placeholder-about.jpg
├── favicon.svg
├── index.html
├── rss.xml
├── sitemap-0.xml
└── sitemap-index.xml
その他のトピック
Image コンポーネント
Astro 公式から、 Image
というコンポーネントが提供されています。
src/
の下に配置された画像を Image
コンポーネントで利用すると、ビルドのタイミングで最適化 (webp
に変換) してくれます。
public/
の下に配置された画像は最適化されないため、 src/
に置くことが推奨されています。
We recommend that local images are kept in src/ when possible so that Astro can transform, optimize and bundle them.
Tailwind CSS
Astro は、 Tailwind CSS をサポートしています。
npx astro add tailwind
で Integration をインストールしてください。
npx astro add tailwind
✔ Resolving packages...
Astro will run the following command:
If you skip this step, you can always run it yourself later
╭────────────────────────────────────────────────────╮
│ npm install @astrojs/tailwind tailwindcss@^3.0.24 │
╰────────────────────────────────────────────────────╯
✔ Continue? … yes
✔ Installing dependencies...
Astro will generate a minimal ./tailwind.config.mjs file.
✔ Continue? … yes
Astro will make the following changes to your config file:
╭ astro.config.mjs ───────────────────────────────╮
│ import { defineConfig } from 'astro/config'; │
│ import mdx from '@astrojs/mdx'; │
│ import sitemap from '@astrojs/sitemap'; │
│ │
│ import tailwind from "@astrojs/tailwind"; │
│ │
│ // https://astro.build/config │
│ export default defineConfig({ │
│ site: 'https://example.com', │
│ integrations: [mdx(), sitemap(), tailwind()] │
│ }); │
╰─────────────────────────────────────────────────╯
✔ Continue? … yes
success Added the following integration to your project:
- @astrojs/tailwind
astro add tailwind
は、以下を実行しています。
npm install @astrojs/tailwind [email protected]
tailwind.config.mjs
(Tailwind CSS 設定ファイル) 生成astro.config.mjs
のintegrations
にtailwind()
を追加
Autoprefixer も一緒にインストールされます。
あとは、以下のように class
に Tailwind で提供されているクラスを書くだけです。
<body>
<Header />
<main class="ml-2">
...
</main>
<Footer />
</body>
Integrations
Integration という仕組みで機能を拡張できます。 上記の Tailwind CSS サポートも Integration の一つです。 詳細は公式ページをご参照ください。
IDE サポート
IDE | 提供元 | 安定性 |
---|---|---|
VS Code | Astro | 安定している |
JetBrains | JetBrains | 2023年11月時点ではやや不安定でした |
まとめ
Astro Islands と Zero JS, by default という考え方は、今後の SSG の大きなトレンドになるかもしれません。
この投稿が、お役に立てば幸いです。