Astro 4.8でのブログシステム完全実装 - AI翻訳とE2Eテストを含む包括的アプローチ
Astro Content Collectionsを基盤とした高性能ブログシステムの実装記録。Google Gemini APIによるAI自動翻訳、Playwright E2Eテスト、レート制限機能を含む包括的な開発プロセスを解説
概要
最近の開発で、Astro 4.8.7を基盤にした包括的なブログシステムを実装した。このシステムはMarkdownを表示するだけでなく、AI自動翻訳、E2Eテスト、そして本格的な運用を見据えた機能までそろえた、現代的な技術ブログプラットフォームだ。
もうWordPressでウィジェットと格闘する時代は終わった。これからはAIに翻訳させて、テストも自動化して、エンジニアは寝ていればいいのだ(嘘)。
技術スタック
コア技術
- Astro 4.8.7: アイランドアーキテクチャによる高速SSG
- TypeScript: 型安全な開発環境
- Tailwind CSS: ユーティリティファーストのスタイリング
- Alpine.js 3.14.0: 軽量フロントエンドJavaScript
ブログ専用機能
- Content Collections: Astroの型安全なコンテンツ管理
- Google Gemini API: AI自動翻訳エンジン
- Playwright: E2Eテスト自動化
- remark-link-card-plus: リッチリンクカード生成
実装の中心となるアーキテクチャ
1. Content Collections設計
従来のMarkdownファイル管理から、AstroのContent Collectionsに切り替えた。もう「あれ?このfrontmatterの書き方で合ってたっけ?」なんて悩まない。
// src/content/config.ts
const blogCollection = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
description: z.string(),
pubDate: z.coerce.date(),
category: z.enum(['ai', 'engineering', 'founder', 'lab']),
tags: z.array(z.string()).default([]),
lang: z.enum(['ja', 'en']).default('ja'),
featured: z.boolean().default(false),
}),
});
利点は次のとおりだ。
- ビルド時の型検証でメタデータエラーを防止
- Hot reloadingでリアルタイム更新
- 自動スキーマバリデーション
2. AI翻訳システムの設計
Google Gemini APIを使って自動翻訳システムを作った。人間の翻訳者に頼むより、ずっと早く終わる。しかも文句も言わない。
// scripts/translate-blog.js
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
const model = genAI.getGenerativeModel({
model: "gemini-2.5-flash-lite-preview-06-17"
});
async function translateBlogPost(japaneseContent) {
const prompt = `Translate the following Japanese blog post to English.
Translate technical terms appropriately and make it readable English:
${japaneseContent}`;
const result = await model.generateContent(prompt);
return result.response.text().trim();
}
特徴は次のとおりだ。
- レート制限: 15リクエスト/分の保守的な制限
- エラーハンドリング: 指数バックオフによるリトライ機能
- YAML安全性: frontmatterの適切なエスケープ処理
3. レート制限システム
APIを安定して運用するために、レート制限の機能を実装した。Googleに「お前、リクエスト送りすぎ!」って怒られないためでもある。
// scripts/rate-limiter.js
class RateLimiter {
constructor({ requestsPerMinute = 15, maxRetries = 3 }) {
this.requestsPerMinute = requestsPerMinute;
this.maxRetries = maxRetries;
this.requestTimes = [];
}
async executeWithRetry(operation, description) {
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
try {
await this.checkRateLimit();
return await operation();
} catch (error) {
if (attempt === this.maxRetries) throw error;
await this.backoff(attempt);
}
}
}
}
4. E2Eテストの実装
Playwrightを使って、ブログ機能の包括的なテストを実装した。「動いてるからヨシ!」じゃダメなのだ。
// e2e/blog.spec.ts
test.describe('Blog functionality', () => {
test('should navigate to Japanese blog', async ({ page }) => {
await page.goto('/blog/');
await expect(page.locator('h1')).toContainText('技術ブログ');
});
test('should display OGP image for blog posts', async ({ page }) => {
await page.goto('/blog/');
const firstPost = page.locator('[data-testid="blog-post"]').first();
await firstPost.click();
const ogImage = page.locator('meta[property="og:image"]');
await expect(ogImage).toHaveAttribute('content', /\/og\/.*\.svg/);
});
});
パフォーマンス最適化
圧縮・最適化設定
// astro.config.mjs
integrations: [
compress({
CSS: true,
HTML: {
'remove-comments': true,
'minify-js': true,
'minify-css': true
},
JavaScript: true,
SVG: true
}),
compressor({
gzip: true,
brotli: true
})
]
画像最適化
- AVIF形式での画像配信
- WebPフォールバック
- レスポンシブ画像サイズ(480w, 800w)
課題と解決策
1. YAML Frontmatterのエスケープ問題
課題は、翻訳されたタイトルや説明文に特殊文字が含まれると、YAMLのパースエラーになることだった。
解決策として、次の関数でエスケープする。
function escapeYAMLString(str) {
return str
.replace(/\\/g, "\\\\") // バックスラッシュを最初にエスケープ
.replace(/"/g, '\\"'); // ダブルクォートをエスケープ
}
2. API レート制限対応
課題は、Gemini APIの制限でリクエストが失敗することだった。
解決策は指数バックオフとインテリジェントなレート制限だ。
- 1秒ベース遅延
- 最大60秒までの段階的遅延
- リクエスト時刻追跡による事前制限
3. E2Eテストの安定性
課題は、動的なコンテンツのせいでテストが不安定になることだった。
解決策はdata-testid属性を使って要素を確実に特定することだ。
運用フロー
新記事投稿プロセス
- 日本語記事作成:
/src/content/blog/ja/にMarkdownファイルを作成 - AI翻訳:
npm run translate src/content/blog/ja/article-name.md - テスト実行:
npm run test:e2e - ビルド:
npm run build - デプロイ: Firebase Hostingへ自動デプロイ
品質保証
- 型チェック: TypeScriptによるコンパイル時チェック
- スキーマ検証: Zodによるコンテンツ検証
- E2Eテスト: Playwrightによる機能テスト
学んだこと
1. Content Collectionsの威力
従来のファイルベース管理と比べて、型安全性と開発体験が劇的に向上した。特に、スキーマを変えたときの影響範囲が明確になったので、安全にリファクタリングできるようになった。
もうTypeScriptが「型が違うぞ!」って怒ってくれるから、実行時エラーでビビることもない。最高だ。
2. AI翻訳の実用性
Google Gemini APIの翻訳品質は技術記事では非常に高く、人手による微調整は最小限で済んだ。ただし、専門用語の一貫性については継続的な改善が必要だ。
「npm」を「エヌピーエム」って訳されたときは笑った。まあ、間違ってはいないけど。
3. E2Eテストの重要性
静的サイトジェネレーターでも、ルーティングやメタデータ生成のような複雑な機能にはE2Eテストが不可欠だと実感した。
「静的サイトだからテストいらないでしょ」なんて思ってた過去の自分を殴りたい。
まとめ
このブログシステムの実装を通じて、現代的なJAMstackアーキテクチャでは次の点が重要だと確認できた。
- 型安全性: Content Collectionsによる設計時エラー防止(もう実行時エラーで泣かない)
- 自動化: AI翻訳とE2Eテストによる運用効率化(人間の仕事を奪っていく)
- パフォーマンス: 積極的な最適化による高速化(爆速の極み)
- 保守性: 明確なアーキテクチャによる持続可能な開発(未来の自分に優しい)
技術ブログプラットフォームとして、記事の執筆から公開までの流れを自動化し、開発者が記事の中身づくりに集中できる環境を作れた。
つまり、これでブログ更新をサボる言い訳ができなくなったということだ。素晴らしい…のか?
この記事の内容を、自社で進めたい方へ
AI受託開発のご相談を、AI受付「Cloudia」でお受けしています。
AI基盤構築・AI受託について詳しく見る関連記事
Gitの履歴から何が分かり、何は分からないか。Griftで4つのOSSを検算した
gitの履歴から、明示した規則で数えられることと、数えられないことを書きます。実在のOSSを4つ、リポジトリ単位で検算しました。人の能力や、記録が完全であることは保証しません。
「点数も順位もつけない」を実装でどう守るか。Grift CLIが次の版で決めた5つの条件
Grift CLIは点数や順位を出さないと約束しています。2026年10月の突合で、注記では順位ではないと書きながら、表はコミット数の多い順に並んでいたことが見つかりました。0.8に課す5つの受入条件を、発注側・SES営業・エンジニアの方に向けて平易に書きます。
AI開発・受託開発の見積もりが属人化する理由〜根拠を会社資産に変える方法
受託開発の見積もりが人によってブレる理由と、工数内訳や前提を残して説明できる見積根拠を作る方法を解説します。見積支援のWebアプリは2026年9月に提供を終えています。いまの相談はAI受託開発・導入支援です。