
Astro製ブログをGitHub Pagesのサブディレクトリに公開する
#プログラム#AstroAstroは、主にSSG (Static Site Generator) として動作し、爆速の静的サイトを生成するライブラリ。
基本的に使いやすいのだけれど、タイトルのような条件でブログを作ろうとしたら微妙に大変だったので、やり方をメモしておく。
ちなみに一応これでちゃんと動くけど、書いているやり方が正しいのかどうかはよく分からず、全然自信がない。あくまでも自分で編み出した方法なので、もしもっと良いやり方があれば教えて欲しい。
結論
- 自サイト内へのリンクや画像等の参照は、リポジトリ名を付与した絶対パスで行えばOK。
- リポジトリ名の記載がコードのあちこちに分散していると面倒なので、まとめると楽。
- そのとき、Astroの環境変数を使えばもっと楽。でもちょっと罠がある。
- ブログの記事(
.md
ファイル)からの画像の読み込みは、.mdx
を利用する。
これで対応を行ったブログのリポジトリと動作するページがあるので、これを参考にしてもらうと分かりやすいかも。
前段の話・条件
タイトル通り、Astroで構築したブログをGitHub Pagesのサブディレクトリに公開するものとする。
さて、GitHub Pagesでは、リポジトリ名でサブディレクトリができる形になって、URLは https://<ユーザ名>.github.io/<リポジトリ名>/
になる。このサブディレクトリというのが曲者で、Astroで対応するためには色々と工夫が必要になる。
逆に言えば、GitHub Pagesを使ったとしても、URLがサブディレクトリを持たない以下の2パターンのいずれかのときはここで書いているような工夫は不要なので、そのままデプロイすれば良い。
- 独自ドメインで運用する。
- リポジトリ名を
<ユーザ名>.github.io
とする。(これだとGitHub Pagesの仕様によりhttps://<ユーザ名>.github.io/
となって、サブディレクトリ構造にならないため)
したがって、GitHub Pagesの通常のドメインのまま、好きなリポジトリ名を付けて公開するときが対象になる。
テンプレートからブログを作成する
ここから実際にブログを作成・修正しつつ手順を説明していくけど、その前提として、マシンへそれなりに新しい Node.js がインストールされている必要がある。詳しいことはAstro公式ドキュメントのインストールの項に書いてあるので、そちらを読むのが確実。
Astroのテンプレートを使ってブログを作成するところから始めるために、適当なディレクトリで以下のコマンドを実行する。
npm create astro@latest
色々質問されるので、それに答えていく。今回は以下のようにした。
- プロジェクトの作成先と名前:
./astro-blog-test
- 使用するテンプレート:blogテンプレート
- 依存するものをインストールするか:Yes
- Gitリポジトリを初期化するか:Yes
- TypeScriptを使うか:Yes
- TypeScriptの厳しさは:Strict
基本的には好きなものやデフォルト項目を選べばいいけど、記事の内容をなぞるときはテンプレートとしてブログを選ぶと分かりやすい。
これでひとまず動く状態となったので、作成されたプロジェクトのディレクトリに移動して以下のコマンドを実行する。
npm run dev
開発サーバが立ち上がるので、http://localhost:3000/
(多分)へWebブラウザでアクセスすれば、ひとまずローカル環境で動いていることが確認できる。
GitHub Pagesへのデプロイ設定
この時点できちんと動かないことを確認するため、早速GitHub Pagesへのデプロイを行う。
本記事では、GitHubへのプッシュで自動的にサイトがビルドされるよう、GitHub Actionsによる自動デプロイを設定していく。
まずは適当にGitHubにパブリックリポジトリを作成し、それをプロジェクトのリモートリポジトリとして登録する。(この記事ではastro-test
としてみた)
それから自動デプロイのために色々と設定していく。やり方は基本的に、Astro公式ドキュメントに書いてある通り。2023年2月時点では未翻訳で英語ではあるけど、書いてある内容は難しくない。
やることをざっくり列挙すると以下の通り。<ユーザ名>
と<リポジトリ名>
は適宜自分のGitHub環境に合わせる。
astro.config.mjs
に、site
とbase
を設定する。site
はhttps://<ユーザ名>.github.io/
base
は/<リポジトリ名>
(頭にスラッシュを入れ、末尾には入れない)
- プロジェクト内に
.github/workflows/deploy.yml
を作成し、公式ドキュメントに記載されているコードを貼り付ける。 - GitHubのリポジトリで、Setting タブの Pages セクションを開く。
- Source で GitHub Actions を選ぶ。
- ローカルのリポジトリをコミットしてプッシュする。
これで自動的にデプロイされ、大体1分くらい経てばGitHub Pagesに公開されるので、https://<ユーザ名>.github.io/<リポジトリ名>
で開けるはず。もしダメならリポジトリのActionsタブを見てビルドの実行結果を確認・対応する。
この時点での状況を確認
ローカル環境
ここまでやって、ローカルで開発サーバを開いていたら(npm run dev
が実行中のままだったら)、ページhttp://localhost:3000/
の表示が「404: Not found」のエラーページになっているはず。
これは先程行ったbase
の設定が効いていて、ページ構造が全部base
で設定したディレクトリ内に存在することになっているため、ルートにはコンテンツが存在しないのが理由。
したがってページを表示するには、http://localhost:3000/
にbase
で設定したフォルダ名を書き加える。それか404の画面に書いてあるフォルダへのリンクをクリックしてもよい。
GitHub Pages
デプロイした結果として、https://<ユーザ名>.github.io/<リポジトリ名>
でページは開けているはず。
ただ、ブラウザの開発ツールでコンソールを見ると、ファビコン(favicon.svg
)が404エラーとなって読み込めていないことが確認できる。ひとまず現時点ではこれで問題ない。
リンクの動作を見る
さてさて、ひとまずトップページは表示されたものの、ここからが問題。ローカルでもGitHub Pagesでも、自サイト内へのリンク(例えば上部の「About」)をクリックすると、404エラーになってアクセスできないことがわかる。
これはアドレスバーを見ると分かる通り、リンク先のURLが正しくないため。サブディレクトリ内にページが存在しているはずなのに、URLがサブディレクトリではないページになっている。
プロジェクト内のソースコードを見れば分かりやすい。ヘッダ部分のコードはsrc/components/Header.astro
であり、この13行目を見ると、Aboutのリンク先が/about
と指定されている。つまりルートからのリンクになっているので、<ドメイン>/about/
を読み込むことになっていて、正しい<ドメイン>/<リポジトリ名>/about/
ではない。サブディレクトリが反映されておらず、存在しないページを表示しようとしているので404エラーとなっている。
このように、Astroはリンクに対してbase
の設定を反映してくれない。これはa
タグのリンクだけでなく、ファビコンへのリンクのようなルートからの絶対パスで記載するものすべてが同様。
以降、これをきちんと動作するように修正していく。
基本的な対応方針
前述の通り、リンクや画像の読み込みが上手く動かないのは、URLが実態と異なっているから。astro.config.mjs
のbase
の設定が反映されるところと反映されないところがあり、自サイト内へのリンクは自分で対応する必要がある。
この対応自体は非常に単純で、ルート「/」から始まっているパスに対して、ディレクトリ名(=リポジトリ名)を頭に付けること。要するに、以下のように書けば良い。
- トップページへのリンク:
/
→/<リポジトリ名>/
- blogディレクトリへのリンク:
/blog
→/<リポジトリ名>/blog
- ファビコンの参照:
/favicon.svg
→/<リポジトリ名>/favicon.svg
ただ、コードにこうやって直接記載するのは色々とよろしくない。例えば将来的にリポジトリ名を変更する場合、ソースコードのあちこちを修正することになってしまう。ということで、実際には後述するようにちょっと工夫を加えた形で対応する。
もう1つ考えられる対応方針(この記事ではやらない方法)
もう1つ単純に思い付くであろうシンプルな対応方針がある。それはリンクを相対パスで指定すること。例えばトップページからblogページへのリンクは ./blog
と書けば良い。
ただこれ、実際にやると色々と面倒なことになる。
- ディレクトリ構造を考慮して、各ページのリンクを記述する必要がある。
- Astroで最終的に生成されるディレクトリ構造は、生成元のディレクトリ構造と異なる。
- ヘッダからのリンクを書くのがとても面倒。
- (これくらいなら、記述が一箇所にまとまるので絶対パスとして書いても良い気がするけど)
- 記事からの画像読み込みに対応できない。
- Astroの仕様により、
.md
で書く記事の中から、相対パスによる画像指定は不可。
- Astroの仕様により、
ということで逆に面倒なので、この記事ではこの対応方法は採用しない。少し工夫した形で絶対パスを指定するやり方を行っていく。
対応の実施
ここから実際に対応していく。
まずはDRY原則に従い、リンクで使うパスを変換する関数を作ることにする。各ページのリンクは、この関数により生成されたパスを使用する。
関数でやりたいことは単純で、例えば/blog
が渡されたら/<リポジトリ名>/blog
を返すだけ。
パス変換関数の作成
src/scripts/util.ts
を作成して、以下のようなコードを書く。中身はまぁ単純。
export function href(s: string): string {
if (s.charAt(0) === '/') {
s = s.substring(1);
}
return `${import.meta.env.BASE_URL}${s}`;
}
import.meta.env.BASE_URL
はAstroの環境変数で、base
で設定している値を取得できる。これにより、リポジトリ名が変わった場合でも設定ファイルでbase
を変更するだけで済み、ソースコードの変更が不要になる。
import.meta.env.BASE_URL
の罠
さて、この関数内で利用しているimport.meta.env.BASE_URL
の値にはちょっとした罠があり、ドキュメントに記載されている使い方が嘘だったりする。
ドキュメントには使い方の例として以下のように書かれている。
<img src=`${import.meta.env.BASE_URL}/image.png`>
しかし実際に使ってみると分かるけど、この変数はなぜか末尾にスラッシュが付いた形で返ってくる。すなわちドキュメント通りに書くと、スラッシュが2つ連続した不正なパスになってしまうという罠がある。
ということで上記のhref()
関数では、引数で渡された1文字目のスラッシュをif
内で削除するとともに、文字列の連結時に間にスラッシュを入れないようにしている。
関数の使い方
この関数を使ってリンクを行うときは、当該ページでhref()
をインポートして、リンクが書かれている部分でhref()
を使うように修正する。
具体的には以下のようになる。
// src/components/Header.astro
import { href } from '../scripts/util'; // (この行だけコードフェンス内)
...(略)...
// <HeaderLink href="/">Home</HeaderLink> を修正
<HeaderLink href={href("/")}>Home</HeaderLink>
// <HeaderLink href="/blog">Blog</HeaderLink> を修正
<HeaderLink href={href("/blog")}>Blog</HeaderLink>
// <HeaderLink href="/about">About</HeaderLink> を修正
<HeaderLink href={href("/about")}>About</HeaderLink>
...(略)...
他にも以下のような部分を同様に修正すれば良い。
src/components/BaseHead.astro
- ソーシャル画像の読み込み部分(14行目):
image = '/placeholder-social.jpg'
→image = href('/placeholder-social.jpg')
- ファビコンの読み込み部分(20行目):
href="/favicon.svg"
→href={href("/favicon.svg")
- ソーシャル画像の読み込み部分(14行目):
src/lauouts/BlotPost.astro
heroImage
の読み込み部分(35行目):src={heroImage}
→src={href(heroImage)}
src/pages/blog/index.astro
- 記事へのリンク部分(45行目):
href={`/blog/${post.slug}/`}
→href={href(`/blog/${post.slug}/`)}
- 記事へのリンク部分(45行目):
これでコミットしてプッシュすれば、リンクがきちんと働くことを確認できる。
記事のMarkdownファイルから読み込む画像にMDXで対応する
これで完璧……と思いきや、これだけではブログ記事での画像読み込みに対応していない。
この時点でデプロイしたGitHub Pagesの方を見ると、例えば「Markdown Style Guide」の記事に埋め込まれている画像が表示されていない。開発者ツールのコンソールを見ると、画像のURLがサブディレクトリなしになっているので404エラーになっていることが分かる。
前項の修正で対応していないので当然と言えば当然ではある。ただ同じように修正しようとしても、.md
ファイルの中ではJavaScriptを書けないので、少し違った対応を行う必要がある。
ここで登場するのがMDX形式の.mdx
ファイル。MDXはマークダウンの中でJSXが書けるようになるファイルフォーマット。JSXが書けるのならJavaScriptも書けるということで、これで対応する。
ちなみにAstroのブログテンプレートでは最初からMDXに対応しているので、非常に簡単。
例えば「First post」の記事の中に画像を表示するなら、src/content/blog/first-post.md
のファイル名をfirst-post.mdx
と拡張子を変えてMDXファイルにする。そして以下のようなコードを記事のどこかに追記する。
import { href } from '../../scripts/util';
<img src={href('/placeholder-hero.jpg')} />
すると画像のパスが解決され、「First post」の記事の中に画像が表示される。これで一件落着。
ここまで書いてきた通り、Astroでサブディレクトリにデプロイするときは自前で色々と対応する必要がある。こんなのbase
の値を元にしてAstro側で自動的に上手いことやってくれれば楽なのに、と思わなくもないけど、色々なケースを考えるとそう一筋縄ではいかないであろうことは容易に想像できるので、ユーザ側に任せる形になっているんだろうと思っている。
ただまぁ、ここまで色々と書いてきたけど、自分がまだAstroをよく分かっていないこともあり、 実はもっとスマートな方法が存在する可能性がかなりあると思っているので、もしそんな方法を知っている人がいれば教えて欲しい。