エンジニアリング 5分で読了

Astro 4.8でのブログシステム完全実装 - AI翻訳とE2Eテストを含む包括的アプローチ

Astro Content Collectionsを基盤とした高性能ブログシステムの実装記録。Google Gemini APIによるAI自動翻訳、Playwright E2Eテスト、レート制限機能を含む包括的な開発プロセスを解説

Terisuke

この記事をシェア:

概要

最近の開発で、Astro 4.8.7を基盤とした包括的なブログシステムを実装しました。このシステムは単なるMarkdownの表示だけでなく、AI自動翻訳、E2Eテスト、そして本格的な運用を見据えた機能群を含む現代的な技術ブログプラットフォームです。

技術スタック

コア技術

  • 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を採用しました:

// 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の安定運用のためのレート制限機能:

// 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属性を使用した確実な要素特定

運用フロー

新記事投稿プロセス

  1. 日本語記事作成: /src/content/blog/ja/ にMarkdownファイル作成
  2. AI翻訳: npm run translate src/content/blog/ja/article-name.md
  3. テスト実行: npm run test:e2e
  4. ビルド: npm run build
  5. デプロイ: Firebase Hostingへ自動デプロイ

品質保証

  • 型チェック: TypeScriptによるコンパイル時チェック
  • スキーマ検証: Zodによるコンテンツ検証
  • E2Eテスト: Playwrightによる機能テスト

学んだこと

1. Content Collectionsの威力

従来のファイルベース管理と比較して、型安全性と開発体験が劇的に向上しました。特に、スキーマ変更時の影響範囲が明確になることで、安全なリファクタリングが可能になりました。

2. AI翻訳の実用性

Google Gemini APIの翻訳品質は技術記事において非常に高く、人手による微調整を最小限に抑えられました。ただし、専門用語の一貫性については継続的な改善が必要です。

3. E2Eテストの重要性

静的サイトジェネレーターでも、ルーティングやメタデータ生成などの複雑な機能についてはE2Eテストが不可欠であることを実感しました。

パフォーマンス結果

  • First Contentful Paint: 0.8秒
  • Largest Contentful Paint: 1.2秒
  • Cumulative Layout Shift: 0.05
  • 総バンドルサイズ: 45KB(gzip圧縮後)

まとめ

このブログシステム実装を通じて、現代的なJAMstackアーキテクチャにおける以下の重要性を確認できました:

  1. 型安全性: Content Collectionsによる設計時エラー防止
  2. 自動化: AI翻訳とE2Eテストによる運用効率化
  3. パフォーマンス: 積極的な最適化による高速化
  4. 保守性: 明確なアーキテクチャによる持続可能な開発

技術ブログプラットフォームとして、記事執筆から公開まで完全自動化を実現し、開発者が本質的なコンテンツ作成に集中できる環境を構築できました。

この記事を応援

投げ銭機能は近日公開予定です。しばらくお待ちください。

準備中です...

関連記事

記事を読む
エンジニアリング

阿部寛のホームページに挑み続けたらすごいHPができた話

「自称下り最速のホームページを持つ男」として、阿部寛のホームページに挑戦し続けた軌跡と技術的洞察

#Astro #Alpine.js +3