はじめに
Astroベースのブログプロジェクトを完全TypeScript化し、GitHub ActionsでCI/CDパイプラインを構築する過程で、予想以上に多くの課題に直面しました。最終的にデプロイ成功までに約50回のコミットが必要でした。この記事では、遭遇したエラーとその解決方法を時系列で詳細に記録します。
プロジェクトの初期状態
- フレームワーク: Astro 5.2.5
- 言語: JavaScript/TypeScript混在
- CI/CD: GitHub Actions (基本的なビルド&デプロイのみ)
- 品質管理ツール: 未設定
Phase 1: TypeScript完全移行
実施内容
すべてのJavaScriptファイルをTypeScriptに移行:
# 移行したファイルsrc/utils/*.js → *.tssrc/config.js → config.tsastro.config.mjs → astro.config.ts遭遇した問題1: astro:content モジュールエラー
// エラー: Cannot find module 'astro:content' or its corresponding type declarationsimport { getCollection } from 'astro:content'解決方法: src/env.d.ts を作成し、型定義を追加
/// <reference types="astro/client" />
declare module 'astro:content' { export type CollectionEntry<C extends keyof typeof collections> = { id: string slug: string body: string collection: C data: InferEntrySchema<C> render(): Promise<{ Content: any headings: any[] remarkPluginFrontmatter: Record<string, any> }> }
type InferEntrySchema<C extends keyof typeof collections> = C extends 'posts' ? { title: string description: string pubDate: Date updatedDate?: Date heroImage?: string category?: string tags: string[] draft: boolean } : never
// 他の必要な型定義...}遭遇した問題2: 型推論エラー
// エラー: Type 'unknown[]' is not assignable to type 'string[]'const tags = [...new Set(posts.flatMap((post) => post.data.tags || []))]解決方法: 明示的な型注釈を追加
const allTags: string[] = posts.flatMap((post: CollectionEntry<'posts'>) => { const postTags: string[] = post.data.tags || [] return postTags})const tags: string[] = [...new Set(allTags)]Phase 2: ESLint v9 移行
遭遇した問題: 設定ファイル形式エラー
ESLint couldn't find an eslint.config.(js|mjs|cjs) fileESLint v9では従来の .eslintrc.json が使えなくなりました。
解決方法: フラットコンフィグ形式に移行
import js from '@eslint/js'import typescriptParser from '@typescript-eslint/parser'import typescriptPlugin from '@typescript-eslint/eslint-plugin'import astroPlugin from 'eslint-plugin-astro'import reactPlugin from 'eslint-plugin-react'import globals from 'globals'
export default [ js.configs.recommended, { files: ['**/*.{js,jsx,ts,tsx}'], languageOptions: { parser: typescriptParser, ecmaVersion: 'latest', sourceType: 'module', globals: { ...globals.browser, ...globals.node, }, }, plugins: { '@typescript-eslint': typescriptPlugin, react: reactPlugin, }, rules: { '@typescript-eslint/no-unused-vars': [ 'error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_', }, ], 'no-unused-vars': 'off', }, }, { files: ['**/*.astro'], plugins: { astro: astroPlugin, }, processor: 'astro/astro', },]Phase 3: Prettier フォーマット問題
遭遇した問題: Markdownファイルのパースエラー
[error] src/content/posts/fixing-theme-switching-issues.md: SyntaxError: Unexpected characterコードブロック内の特殊文字が原因でPrettierがパースに失敗。
解決方法: .prettierignore に問題のあるファイルを追加
src/content/posts/fixing-theme-switching-issues.mdsrc/content/posts/tailwind-troubleshooting.mdsrc/pages/archive.astroPhase 4: Firebase 条件付き初期化
遭遇した問題1: CI環境でのFirebaseエラー
Firebase: Error (auth/invalid-api-key)環境変数が設定されていないCI環境でFirebase初期化が失敗。
解決方法: 条件付き初期化の実装
const hasFirebaseConfig = !!( import.meta.env.PUBLIC_FIREBASE_API_KEY && import.meta.env.PUBLIC_FIREBASE_AUTH_DOMAIN && import.meta.env.PUBLIC_FIREBASE_PROJECT_ID)
let app: FirebaseApp | null = nulllet auth: Auth | null = nulllet googleProvider: GoogleAuthProvider | null = null
if (hasFirebaseConfig) { try { const firebaseConfig = { apiKey: import.meta.env.PUBLIC_FIREBASE_API_KEY, authDomain: import.meta.env.PUBLIC_FIREBASE_AUTH_DOMAIN, projectId: import.meta.env.PUBLIC_FIREBASE_PROJECT_ID, // ... }
app = initializeApp(firebaseConfig) auth = getAuth(app) googleProvider = new GoogleAuthProvider() } catch (error) { console.warn('Firebase initialization failed:', error) app = null auth = null googleProvider = null }}
export { auth, googleProvider, hasFirebaseConfig }遭遇した問題2: TypeScript 型エラー
// エラー: Argument of type 'Auth | null' is not assignable to parameter of type 'Auth'await signInWithPopup(auth, googleProvider)解決方法: null チェックの追加
const handleLogin = async () => { if (!auth || !googleProvider) return try { await signInWithPopup(auth, googleProvider) } catch (error) { console.error('Error signing in with Google', error) }}遭遇した問題3: SSRビルドエラー
Unable to render LoginButton!Does not conditionally return `null` or `undefined` when rendered on the server.サーバーサイドレンダリング時にコンポーネントが null を返すことによるエラー。
解決方法: client:only ディレクティブの使用
<!-- src/components/Header.astro --><!-- 変更前 --><LoginButton client:load />
<!-- 変更後 --><LoginButton client:only="react" />Phase 5: CI/CD パイプライン最適化
最終的なGitHub Actions設定
name: Deploy to GitHub Pages
on: push: branches: [main] pull_request: branches: [main] workflow_dispatch:
permissions: contents: read pages: write id-token: write
jobs: quality: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4
- name: Setup pnpm uses: pnpm/action-setup@v4 with: version: 10.2.1
- name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' cache: 'pnpm'
- name: Install dependencies run: pnpm install --frozen-lockfile
- name: Type check run: pnpm run type-check
- name: Lint check run: pnpm run lint
- name: Format check run: pnpm run format:check
- name: Build test run: pnpm run build
build: if: github.event_name == 'push' && github.ref == 'refs/heads/main' needs: quality runs-on: ubuntu-latest steps: - name: Checkout your repository using git uses: actions/checkout@v4
- name: Install, build, and upload your site uses: withastro/action@v3 with: package-manager: pnpm@10.2.1
deploy: if: github.event_name == 'push' && github.ref == 'refs/heads/main' needs: build runs-on: ubuntu-latest environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4重要な学び
1. ローカルテストの重要性
# プッシュ前に必ず実行pnpm run type-checkpnpm run lintpnpm run format:checkpnpm run build2. 段階的な移行
一度にすべてを変更するのではなく、以下の順序で段階的に実施:
- TypeScript移行
- 型エラー修正
- ESLint設定
- Prettier設定
- CI/CD統合
3. エラーメッセージの詳細な読み取り
特にTypeScriptのエラーは、エラーメッセージを丁寧に読むことで解決の糸口が見つかります。
4. 環境差異への対応
開発環境とCI環境の違いを考慮した実装が必要:
- 環境変数の有無
- OSの違い(Windows vs Linux)
- Node.jsバージョン
統計データ
- 総コミット数: 約50回
- 修正にかかった時間: 約8時間
- 修正したファイル数: 30以上
- 解決したエラー種別: 7種類
まとめ
CI/CDパイプラインの構築は、単にツールを設定するだけでなく、プロジェクト全体の品質向上につながる重要なプロセスです。多くのエラーに遭遇しましたが、それぞれが貴重な学習機会となりました。
特に重要なのは:
- エラーを恐れない - エラーは学習の機会
- 段階的に進める - 一度にすべてを変更しない
- ローカルで確認 - プッシュ前の確認を徹底
- ドキュメントを残す - 後で同じ問題に遭遇した時のために
このプロジェクトを通じて、堅牢なCI/CDパイプラインの価値を改めて実感しました。初期設定は大変ですが、長期的には開発効率と品質の大幅な向上につながります。
