🧊

AstroでSSGする場合の個人的ベストプラクティス

個人的なやつなので、すべての案件にハマるわけではないはず。

今回はたまたまAstroを選んでるけど、他のものを使ってSSGする場合にも、ある程度は当てはまる内容かと。

データの取得コマンドとSSGのコマンドを分ける

ビルドコマンドを叩いた時に、

  • SSGのフローの一貫としてデータを`fetch()`し、それを使ってページ生成

というのではなく、

  • データを取得し、ローカルにキャッシュするコマンド
  • そのキャッシュを使って、SSGでページ生成するコマンド

という2段構えにするということ。

こうすることで、ローカルでの開発時にネットワーキングしなくて済む。
データ取得のコマンドを賢く作れば、毎回一括ダウンロードもしなくてよくなるし。

アーキテクチャにSSGを採用した場合、遅かれ早かれCMSからすべてのデータを引っこ抜くことになるので、そこだけを個別に最適化できるようにしたほうが絶対に幸せになれる。CMSに対してGraphQLで個別に・・とか本当にいらなくて、`all.json`だけあればあとはこっちでよしなに整形する。

そういう意味でも、各CMSベンダー各位がそれ用のCLIツールとか作ってくれていいのにって思ってる。だいたい手動でページング書かないといけなくてだるいので。

下書き記事のプレビューは、CSRする専用のページを作る

CMSへのアクセスに必要なキーは、URLのパラメータにするなりLocalStorageにしまうなりすればよい。

こうすれば、

  • 公開ページと同様のコンポーネントを使える
  • 下書きページのためだけにビルドパイプラインを増やしてBasic認証を・・・とか全部いらない

SSG時に期待するであろう記事データを見て、静的にするか動的にするか書くだけ。

{Boolean(article)
  ? <StaticRenderer {article} />
  : <DynamicRenderer client:only />
}

ディレクティブで`client:only`にしておけばよい。

Pagesにロジックを書かない

`pages/*.astro`でJSのコードをあれこれ書かないということ。

代わりに全部それ用の`.js`に逃がしてそこで書く。

---
export { getStaticPathsForArticlePages as getStaticPaths } from "../../data/index.js";

import { setupArticlePage } from "../../data/index.js";

const { article, relatedArticles, allCategories } = await setupArticlePage(Astro);
---

<!-- 以降はそのまま -->


ただし、`Astro.glob()`だけは逃がせないらしい。

Make Astro global available outside of *.astro files · Discussion #190 · withastro/rfcs · GitHub

これは内部実装のViteプラグインの都合かと。

現状、AstroコンポーネントのDXは微妙であり、VSCode以外のエディタにとってはただのテキストファイルであることも踏まえると、まあ妥当かなーって。

その他の個人的なもの

  • `/public`は、favicon類くらいしか置かない
  • `/layouts`は使わない
    • もちろんサイト構成にもよるけど
    • 基本的にレイアウトを共有する旨味はないと思ってる派閥
    • 特定の幅高さなどをCSS Variablesで共有するのはアリ(というかそれだけでいい)
  • `client:*`のディレクティブを使うとき、一緒に渡すPropsは公開されてしまってよいものか確認する
    • Partial Hydrationのために、JSONがそのままクライアントに落ちることになるので
    • SSRでは問題にならない最適化してない巨JSONが、CSRでは問題になるってこと

まとめ

Astroは、0JSに軸足を置いてるってところが個人的には推しポイント。ただそれがゆえに、取り回しのいいフレームワークコンポーネントに寄せてコードを書いてしまうと、最適化しようとしたときに不便っていう二律背反があり、そこだけディレクトリ構造とか悩ましい。

それはそれとして、来月のメジャーリリース時には、DXまわりがもっと快適になってるといいな〜。