公開日:
更新日:
15 min read
技術革新Astroサイトの記事を自動公開する - Cloudflare PagesとGitHub Actions連携ガイド

このウェブサイトは、astroで構築しています。いわゆる「海」だけの静的サイトで、 ReactやVue.jsのような「島」つまりアイランドは使っていません。 いずれ使ってみたいとは思います。 これをcloudflare pagesでホスティングしています。
さらば、WordPress
それまではWordPressで100件以上ものシステムを開発・運用してきましたが、使い込めば使い込むほど、WordPressの嫌な面が目立つようになりました。 特にプラグインとバージョンアップです。 プラグインはセキュリティリスクになることが多いために、頻繁にバージョンアップしますが、それが原因でシステムが動かなくなることが珍しくありません。 本体のバージョンアップでも同じです。 しかも、過去に様々な脆弱性があったために、今でもそれを攻撃してくる輩が多く、対処するためにもバージョンアップは欠かせません。
疲れるんです。重いんです。面倒くさいんです。
外注に制作を依頼したホームページ(WordPress)10件が、納期数日前に攻撃されてすべて消えた、なんて漫画みたいなことも実際にありました。 このとき、同じ業者に委託していた、納期3週間前のECサイトも3つほど消えたのですが、こちらは、そもそも開発すらしていなかったのを、サイバー攻撃のせいにされた気配が濃厚でした。 ホームページは何とか復帰しましたが、その後、業者とは連絡が取れなくなりました。
ここからが修羅場です…ああ、思い出したくない。 だって、納品されたシステムのURLをたたいたら、WordPressのウェルカムページが表示されたんです… そういえば…ああ、もういいや。 WordPressはつらい思い出ばかりだ。
姑息なphpも好きじゃない
加えて、私はphpが好きではない。php3の頃から使っていますが、あのいい加減な仕様が嫌いです。 クラスに動的にメンバーを追加できるとか、便利さよりもバグの温床のリスクの方がはるかに多いです。 クラスにメンバー追加したきゃ、隠れてこそこそやるんじゃなくて、堂々と継承して拡張して使いやがれ!とジジイは思ってしまうのです。 その点、C++の方が王道を行ってると感じてしまう。
こんにちわ、astro
攻撃されなきゃいいんだろ? 動的サイトだから面倒なんだろ? このような短絡的な結論に至った私は、以前から興味があった静的ジェネレーターを使ってみたいと思いました。 いくつか試してみました。
- Hugo : よく分からないし、Go言語も知らない。大規模サイト用と書いてある。Go言語はあまりそそられない。→却下
- Jekyll : Rubyって使ったこともないのに、なんだか好きになれなくて、それでもがんばってみた。Jekyllは使いにくかった。→却下
- astro : 分かりやすいし、node.jsとtypescriptは前からやってみたかった。→採用
さらに以前から気になっていた「サーバーレス」を試してみたかったので、cloudflare pagesを使うことにしました。 そうしたら、なんということでしょう! astroで記事を書いて、GitHubリポジトリにpushすると、Cloudflareが自動デプロイしてくれるのですごく便利です。 WordPressでの苦労が嘘みたいです。静的ページだから、攻撃されるリスクもないし、デプロイも楽だし、サーバーレスだからコストもかからないし、速いし、SEOもいいし、いいことづくめです。 おまけに無料!!!
爺は満足ぢゃ!
そうやって、がつがつと記事を書いているうちに、公開の手間が面倒臭くなってきました。 WordPressはCMSなので、公開日時を指定しておけば、指定した日時に自動公開してくれます。 ですがastroは静的サイトジェネレーターなので、そういう機能はありません。
ないんです。
こんな便利なもの、世界中で使われているなら、自動公開のニーズも高いはずです。 調べてみたら、同じことをしている人がいたのですが、記事がいまいちわかりにくかったので、整理したものをここに記録しました。
自動公開の仕組み
astroで記事を書いて、GitHubリポジトリにpushすると、Cloudflareが自動デプロイしてくれる機能を利用します。 Cloudflare pagesのデプロイフックなるものを利用します。 それをGitHub Actionsを使って自動デプロイする仕組みです。
astro側の設定
- フロントマターに公開日時を追加します。
- フロントマターに従って、記事の公開を制御する機能を追加します
フロントマターの設定
私の場合、すでにフロントマターに公開日時を設定しているので、それを利用します。 ない場合は追加するといいでしょう。
---
title: 'システム開発の実際(中編):コーディングとプログラミング'
description: '1990年代初頭のシステム開発現場におけるプログラマーとSEの違い、開発環境の実態を記録。'
pubDate: '2024-12-18T00:00:00.000Z'
updatedDate: '2024-12-18T00:00:00.000Z'
heroImage: '/src/assets/images/blog/tech015-hero.webp'
headline: 'コーディングとプログラミング、PGとSEの違い - 1990年代初頭のシステム開発現場から'
category: '技術文化史'
tags: ['システム開発', 'COBOL', 'UNIX', 'パンチカード', '技術史']
draft: false
---
フロントマターの情報はsrc/content/config.ts
に記述してあります。
import { defineCollection, z } from 'astro:content';
import { CATEGORIES } from '@/data/categories';
const blog = defineCollection({
// Type-check frontmatter using a schema
schema: ({ image }) =>
z.object({
title: z.string().max(80),
description: z.string(),
// Transform string to Date object
pubDate: z
.string()
.or(z.date())
.transform((val) => new Date(val)),
updatedDate: z
.string()
.or(z.date())
.transform((val) => new Date(val)),
heroImage: image(),
category: z.enum(CATEGORIES),
tags: z.array(z.string()),
draft: z.boolean().default(false),
keywords: z.string().optional(), // 追加
headline: z.string().optional(),
series: z.string().optional(),
seriesIndex: z.number().optional(),
}),
});
export const collections = { blog };
pubDate
とdraft
を利用します。
公開処理の追加
utils/index.tsにgetAllPosts()関数を追加して、日付制御を行います。 さらに、元々あった下記の関数をオーバーライドして、getAllPosts()を利用するように変更しました。
- getPosts()
- getCategories()
- getTags()
- getPostByTag()
- filterPostsByCategory()
// src/utils/index.ts
import { getCollection } from 'astro:content';
export { sluglify, unsluglify } from './sluglify';
export { cn } from './cn';
export { remarkReadingTime } from './readTime';
// 記事の取得と公開制御の関数
export const getAllPosts = async () => {
const posts = await getCollection('blog', ({ data }) => {
const isNotDraft = import.meta.env.PROD ? data.draft !== true : true;
const isNotFuture = import.meta.env.PROD
? data.pubDate.valueOf() <= new Date().valueOf()
: true;
return isNotDraft && isNotFuture;
});
// 日付でソート
return posts.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
};
// 既存の関数を修正
export const getPosts = async (max?: number) => {
const posts = await getAllPosts();
return max ? posts.slice(0, max) : posts;
};
export const getCategories = async () => {
const posts = await getAllPosts();
const categories = new Set(posts.map((post) => post.data.category));
return Array.from(categories);
};
export const getTags = async () => {
const posts = await getAllPosts();
const tags = new Set(posts.flatMap((post) => post.data.tags));
return Array.from(tags);
};
export const getPostByTag = async (tag: string) => {
const posts = await getAllPosts();
return posts.filter((post) => post.data.tags.includes(tag));
};
export const filterPostsByCategory = async (category: string) => {
const posts = await getAllPosts();
return category.toLowerCase() === 'all'
? posts
: posts.filter((post) => post.data.category.toLowerCase() === category.toLowerCase());
};
このサイトではgetCollection()
でblog
を指定しています。
これはsrc/content/config.ts
の最後の行でblog
と定義しているものです。
もしもここが
export const collections = { post };
だったら、getAllPosts()のgetCollection()の引数はpost
になります。
// 記事の取得と公開制御の関数
export const getAllPosts = async () => {
const posts = await getCollection('post', ({ data }) => {
cloudflare pagesの設定
1. デプロイフックの作成
- Cloudflareの管理画面で「Workers & Pages」を選択
- 「設定」タブを開く
- ページ下部の「デプロイフック」セクションを探す
- デプロイフックの右側の+をクリック
2. フック情報の入力
- デプロイフック名:
daily-deploy
など分かりやすい名前 - ブランチ:
main
(または使用しているブランチ名) - 「保存」ボタンをクリック
3. Webhook URLの取得
- 作成後、デプロイフックのURLが表示される
- このURLをコピーして保管
注意点
- デプロイフックURLは公開しないように注意が必要です
- テスト用の
curl
コマンドも提供されるので、動作確認に使用できます:シェルで実行すれば結果が表示されます。
GitHub Actionsの設定
- このURLを
CLOUDFLARE_DEPLOY_WEBHOOK
として、GitHubのシークレットに設定する
GitHub Actionsでデプロイフックを設定する手順を説明します。
GitHubシークレットの設定手順
1. GitHubリポジトリの設定画面へアクセス
- リポジトリのページを開く
- 「Settings」タブを選択
- 左メニューから「Secrets and variables」→「Actions」を選択
2. シークレットの追加
- 「New repository secret」をクリック
- 名前:
CLOUDFLARE_DEPLOY_WEBHOOK
- 値:Cloudflareから提供されたデプロイフックURL
- 「Add secret」をクリック
注意点
- シークレットの値は一度保存すると表示されません
- シークレットの値を更新する場合は、再度全体を入力する必要があります
- シークレットはリポジトリの管理者権限が必要です
これにより、安全にデプロイフックURLを管理しながら、自動デプロイを実行できます。
GitHub Actionsワークフローの設定
GitHub Actionsのワークフローを設定する手順を説明します。
基本的な設定手順
1. ディレクトリとファイルの作成
- リポジトリのルートに
.github/workflows
ディレクトリを作成 - ワークフローファイル
daily-deploy.yaml
を作成
2. ワークフローファイルの作成
name: 'Daily Deploy'
on:
schedule:
- cron: '0 0 * * *' # 毎日UTC 00:00に実行
workflow_dispatch: # 手動実行用
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy
run: curl -X POST -d {} ${{ secrets.CLOUDFLARE_DEPLOY_WEBHOOK }}
3. ワークフローの実行許可
- リポジトリの Settings → Actions → General で Actions が有効にする
- プライベートリポジトリなので、“Allow aqz-saito actions and reusable workflows”を選択
こうすることで、.github/workflows
ディレクトリ内のyamlファイルが自動的に実行されます。
今後、actions/checkout@v4 など、信頼できる公式アクションを使用する必要がある場合は、 “Allow aqz-saito, and select non-aqz-saito, actions and reusable workflows”に変更します。
注意点
- ファイルは必ず
.github/workflows
ディレクトリに配置 - ファイル拡張子は
.yml
または.yaml
を使用 - インデントは正確に設定する必要がある
- シークレット情報は必ずGitHubのシークレット機能を使用
これらの手順に従うことで、自動デプロイのワークフローを設定できます。
エラーログ
GitHub Actionsのワークフローのエラーログは以下の場所で確認できます:
- GitHub上での確認方法:
- リポジトリのトップページ → 「Actions」タブをクリック
- 実行されたワークフローの一覧が表示される
- エラーのあるワークフローは❌マークで表示
- ワークフローをクリックすると詳細なログが表示
- 各ステップを展開して詳細なエラーメッセージを確認可能
- 具体的なパス:
https://github.com/[アカウント名]/[リポジトリ名]/actions
- ログの確認ポイント:
- エラーが発生したステップが赤く表示される
- エラーメッセージと発生時のコンテキスト
- 実行環境の情報
- 各コマンドの実行結果
- 環境変数(機密情報は非表示)
- 通知設定:
- リポジトリの「Settings」→「Notifications」
- ワークフローの失敗時にメール通知を受け取れる
- Slackなどの外部サービスとの連携も可能
- デバッグ用のTIPS:
# デバッグ情報を出力するステップの例
steps:
- name: Debug
if: failure() # エラー時のみ実行
run: |
pwd
ls -la
env
エラー発生時は、ログを確認して問題の箇所を特定し、必要な修正を行います。