Astroを触ってみて、これはいいかも…と思い、業務でも何度か使ってみました。感想と、いくつか知見を書いておきます。
所感
まず、Next.jsなどと違い(基本的には)JSフリーの状態でビルドされるのが良いです。「納品後はこっちで更新したい」と言われることも多く、お客様の手で変更できないのはNGな案件が結構多いんですよね…。逆に納品後も保守契約を結ぶなどしてずっと手元で触れるなら、そこまで重要ではなさそう。
記法はかなりとっつきやすいので、コーディングはたまにやる程度のデザイナーとも協業しやすそうです。
---
import '@/styles/global.css';
import { SEO } from 'astro-seo';
import Footer from '@/components/Footer.astro';
import Header from '@/components/Header.astro';
import { config } from '@/config';
export interface Props {
title?: string;
description?: string;
}
const { title, description } = Astro.props;
---
<!doctype html>
<html lang="ja" class="">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width" />
<SEO
title={`${title ? `${title} | ` : ''}${config.siteMetadata.name}`}
description={description || config.siteMetadata.description}
/>
</head>
<body class="">
<Header />
<slot />
<Footer />
</body>
</html>
また、getStaticPaths()
から直に変数をテンプレートへ渡せるのが良いですね。Next.jsだと別途getStaticProps()
で取ってこないといけないので。
---
export async function getStaticPaths() {
const data = await fetch('...').then((response) => response.json());
return data.map((post) => {
return {
params: { id: post.id },
props: { post },
};
});
}
const { id } = Astro.params;
const { post } = Astro.props;
---
<h1>{id}: {post.name}</h1>
個人的にいいなと思ったのは名前付きスロットです。
---
import Header from './Header.astro';
import Logo from './Logo.astro';
import Footer from './Footer.astro';
const { title } = Astro.props
---
<div id="content-wrapper">
<Header />
<slot name="after-header"/> <!-- `slot="after-header"` 属性を持つ子要素はここに入ります。 -->
<Logo />
<h1>{title}</h1>
<slot /> <!-- `slot`属性をもたない子要素、`slot="default"`属性を持つ子要素はここに入ります。 -->
<Footer />
<slot name="after-footer"/> <!-- `slot="after-footer"` 属性を持つ子要素はここに入ります。 -->
</div>
---
import Wrapper from '../components/Wrapper.astro';
---
<Wrapper title="フレッドのページ">
<img src="https://my.photo/fred.jpg" slot="after-header">
<h2>フレッドについて</h2>
<p>ここでは、フレッドについて紹介します。</p>
<p slot="after-footer">Copyright 2022</p>
</Wrapper>
サイドバーにバナーを追加したいとか、このページだけJSを追加したいとか、ちょっとした差分も楽に書けます。
反面う〜んと思ったのは、生成されるCSSファイルのファイル名が適当というか。生成後のコードは触らない前提なので普通は問題ないんですが、is:global
を付けて書いたCSSが404.css
に書き出されたりします。納品後お客様が触る案件では紛らわしいので、CSSを全てグローバルで書き、まとめて書き出されるようにしました。もっとうまいやり方がありそう🤔
export default defineConfig({
vite: {
build: {
rollupOptions: {
output: {
entryFileNames: `assets/scripts/[name].js`,
chunkFileNames: `assets/scripts/[name].js`,
assetFileNames: (asset) => {
if (/\.css$/.test(asset.name ?? '')) {
return 'assets/styles/global.css';
} else if (/\.(jpe?g|png|svg)$/.test(asset.name)) {
return 'assets/images/[name].[ext]';
}
return 'assets/[name].[ext]';
},
},
},
},
},
});
追記
Viteのドキュメントを見ていたらCSSを分割するかどうかのオプション項目を見つけました。これをfalse
にすることで、style.cssに書き出されます。
export default defineConfig({
vite: {
build: {
cssCodeSplit: false,
}
},
});
ちょっとしたテクニック
テクニックというほどでもないんですが、「あれできないの?」をまとめてみました。
下層ページURLを「page.html」にしたい
build.format
がfile
の場合、src/pages/page.astro
がdist/page.html
にビルドされます。
export default defineConfig({
build: {
format: 'file',
},
});
他の端末からローカルサーバを確認したい
server.host
をtrue
にすると、スマホや他のPCなどからアクセスできるURLを発行してくれます。
export default defineConfig({
server: {
host: true,
},
});
astro dev
するとこんな感じ。
🚀 astro v2.0.2 started in 696ms
┃ Local http://localhost:3000/
┃ Network http://xxx.xxx.xx.xxx:3000/
devとbuildで条件分岐したい
環境変数が用意されていて、それで判定できます。
ちなみに環境変数を追加したい場合はプロジェクトディレクトリに.env
を作成すれば自動的に読み込んでくれます。
Facebookを埋め込むと動かない
試してないですがおそらくTwitterのツイートボタンもかな? JSでゴニョゴニョするタイプの埋め込みパーツが動かなかったので、set:html
で。
set:html
は要素の中に渡した文字列をHTMLソースコードとして埋め込んでくれます。ブログの本文などに使えますね。<Fragment/>
はラッパーを生成せずset:html
で指定されたコードをそのまま書き出します。
<Fragment
set:html={`
<div id="fb-root"></div>
<script async defer crossorigin="anonymous" src="https://connect.facebook.net/ja_JP/sdk.js#xfbml=1&version=v16.0" nonce="pt9CTwsx"></script>
<div class="fb-page" data-href="https://www.facebook.com/facebook" data-tabs="timeline" data-width="" data-height="" data-small-header="false" data-adapt-container-width="true" data-hide-cover="false" data-show-facepile="true"><blockquote cite="https://www.facebook.com/facebook" class="fb-xfbml-parse-ignore"><a href="https://www.facebook.com/facebook">Facebook</a></blockquote></div>
`}
/>
Sitemapから特定のページを除外したい
@astrojs/sitemap
インテグレーションで特定のページを除外します。たとえば、外部サービスのテーマをコーディングしたけど公開はしないからSitemapには含めないとか。
第一引数page
にはhttps://www.example.com/index.html
のような本番の絶対パスが入っています。これをmatch関数などで当てはまらないものをフィルターして返すようにします。
export default defineConfig({
integrations: [
sitemap({
filter: (page) => !page.match(/\/(blog)\/$/),
}),
],
});