このリポジトリのslidesディレクトリにあるMarkdownファイルを、Cloudflare Pagesに自動デプロイするための設定手順です。

🔧 初回設定

1. Cloudflare Pages プロジェクトの作成

  1. Cloudflare Dashboard にログイン
  2. Workers & PagesCreate a project をクリック
  3. Pages直接アップロードを使用する を選択
  4. プロジェクト名を入力(例: my-slides
  5. プロジェクトを作成

2. Cloudflare API トークンの取得

  1. Cloudflare Dashboard → My ProfileAPI Tokens
  2. Create Token をクリック
  3. Custom token を選択
  4. 以下の権限を設定:
    • Zone:Zone:Read
    • Zone:Page Rules:Edit
    • Account:Cloudflare Pages:Edit
  5. Account resources: Include - All accounts
  6. Zone resources: Include - All zones
  7. トークンを生成してコピー

3. Account ID の取得

  1. Cloudflare Dashboard のアカウントのダッシュボードのURLから Account ID をコピー

4. GitHub Secrets の設定

リポジトリの SettingsSecrets and variablesActionsRepository secretsNew repository secret で以下のSecretを追加:

Secret名
CLOUDFLARE_API_TOKEN手順2で取得したAPIトークン
CLOUDFLARE_ACCOUNT_ID手順3で取得したAccount ID
CLOUDFLARE_PROJECT_NAME手順1で作成したプロジェクト名

📝 スライドの作成

基本的なファイル構造

slides/
├── themes/
│   └── custom.css
└── your-slide.md

Markdownファイルのテンプレート

---
marp: true
theme: custom
title: スライドのタイトル
description: スライドの説明
author: あなたの名前
---
 
# タイトル
 
内容
 
---
 
## セクション
 
内容
 
---
 
<!-- _class: lead -->
 
# 終了スライド

🚀 デプロイ方法

.github/workflowに追加

name: Deploy Slides to Cloudflare Pages
on:
  workflow_dispatch:
 
permissions:
  contents: read
  actions: read
  deployments: write
 
concurrency:
  group: slides-deploy
  cancel-in-progress: true
 
jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    timeout-minutes: 10
 
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
 
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
 
      - name: Install Marp CLI
        run: |
          echo "📦 Installing Marp CLI..."
          npm install -g @marp-team/marp-cli
          echo ""
          echo "✅ Marp CLI installed successfully"
          echo "Version information:"
          marp --version
          echo ""
          echo "Help information:"
          marp --help | head -10
 
      - name: Create output directory
        run: mkdir -p public
 
      - name: Convert Markdown slides to HTML
        run: |
          echo "🔍 Checking workspace structure..."
          echo "Current directory: $(pwd)"
          echo "Workspace contents:"
          ls -la
          
          echo ""
          echo "📁 Slides directory contents:"
          if [ -d "slides" ]; then
            find slides -type f | head -10
          else
            echo "❌ slides directory not found!"
          fi
          
          # Check if slides directory exists and has markdown files
          if [ ! -d "slides" ]; then
            echo "Error: slides directory not found"
            exit 1
          fi
          
          # Count markdown files
          md_files=$(find slides -maxdepth 1 -name "*.md" -type f | wc -l)
          if [ "$md_files" -eq 0 ]; then
            echo "Warning: No markdown files found in slides directory"
            echo "Creating a placeholder index page"
            mkdir -p public
            cat > public/index.html << 'EOF'
            <!DOCTYPE html>
            <html><head><title>No Slides</title></head>
            <body><h1>No slides found</h1><p>Add .md files to the slides directory.</p></body>
            </html>
          EOF
            exit 0
          fi
          
          echo "Found $md_files markdown file(s) to process"
          
          # List the markdown files to be processed
          echo ""
          echo "📝 Markdown files found:"
          find slides -maxdepth 1 -name "*.md" -type f | while read file; do
            echo "   - $file ($(wc -l < "$file") lines)"
          done
          
          echo ""
          echo "🔨 Starting Marp conversion..."
          
          # Convert slides to HTML (output to default location)
          if [ -d "slides/themes" ]; then
            echo "Using custom theme directory: slides/themes"
            echo "Theme files:"
            find slides/themes -name "*.css" | while read theme; do
              echo "   - $theme"
            done
            echo "Running: marp slides/*.md --html --theme-set slides/themes --allow-local-files"
            if ! marp slides/*.md --html --theme-set slides/themes --allow-local-files; then
              echo "❌ Marp conversion failed with custom theme, trying with default theme..."
              marp slides/*.md --html --allow-local-files
            fi
          else
            echo "Using default theme"
            echo "Running: marp slides/*.md --html --allow-local-files"
            if ! marp slides/*.md --html --allow-local-files; then
              echo "❌ Marp conversion failed!"
              echo "Trying individual file conversion..."
              
              # Try converting files individually as fallback
              for md_file in slides/*.md; do
                if [ -f "$md_file" ]; then
                  echo "Converting: $md_file"
                  marp "$md_file" --html --allow-local-files || echo "Failed to convert $md_file"
                fi
              done
            fi
          fi
          
          echo ""
          echo "📁 Checking generated files..."
          echo "Files in slides directory after conversion:"
          ls -la slides/
          
          # Move generated HTML files to public directory
          echo ""
          echo "🚚 Moving HTML files to public directory..."
          mkdir -p public
          
          # Find and move all HTML files from slides directory
          html_files_found=0
          for html_file in slides/*.html; do
            if [ -f "$html_file" ]; then
              filename=$(basename "$html_file")
              echo "Moving: $html_file -> public/$filename"
              mv "$html_file" "public/$filename"
              html_files_found=$((html_files_found + 1))
            fi
          done
          
          echo "Moved $html_files_found HTML file(s) to public directory"
          
          echo ""
          echo "✅ Marp conversion completed"
          echo "Generated files in public directory:"
          ls -la public/ || echo "No files generated"
 
      - name: Create index page
        run: |
          # Skip if no HTML files were generated
          html_count=$(find public -maxdepth 1 -name "*.html" -type f | wc -l)
          if [ "$html_count" -eq 0 ]; then
            echo "No HTML files found, skipping index generation"
            exit 0
          fi
          
          cat > public/index.html << 'EOF'
          <!DOCTYPE html>
          <html lang="ja">
          <head>
              <meta charset="UTF-8">
              <meta name="viewport" content="width=device-width, initial-scale=1.0">
              <title>Slides</title>
              <style>
                  body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
                  h1 { color: #333; }
                  .slide-list { list-style: none; padding: 0; }
                  .slide-item { margin: 10px 0; padding: 15px; border: 1px solid #ddd; border-radius: 5px; }
                  .slide-item a { text-decoration: none; color: #0066cc; font-weight: bold; }
                  .slide-item a:hover { text-decoration: underline; }
                  .slide-meta { font-size: 0.9em; color: #666; margin-top: 5px; }
              </style>
          </head>
          <body>
              <h1>📊 Slides</h1>
              <ul class="slide-list">
          EOF
          
          # Generate list of slides
          for html in public/*.html; do
            if [ "$html" != "public/index.html" ] && [ -f "$html" ]; then
              filename=$(basename "$html" .html)
              filesize=$(du -h "$html" | cut -f1)
              modified=$(date -r "$html" "+%Y-%m-%d %H:%M")
              echo "                  <li class=\"slide-item\">" >> public/index.html
              echo "                      <a href=\"$filename.html\">$filename</a>" >> public/index.html
              echo "                      <div class=\"slide-meta\">Size: $filesize | Modified: $modified</div>" >> public/index.html
              echo "                  </li>" >> public/index.html
            fi
          done
          
          cat >> public/index.html << 'EOF'
              </ul>
              <p><small>Generated automatically from markdown files in the slides directory.</small></p>
          </body>
          </html>
          EOF
          
          echo "Generated index page with $(find public -maxdepth 1 -name "*.html" -not -name "index.html" | wc -l) slide(s)"
 
      - name: Debug Cloudflare credentials
        run: |
          echo "🔍 Debugging Cloudflare configuration..."
          
          # Check if secrets are set (without revealing values)
          if [ -z "${{ secrets.CLOUDFLARE_API_TOKEN }}" ]; then
            echo "❌ CLOUDFLARE_API_TOKEN is not set"
          else
            echo "✅ CLOUDFLARE_API_TOKEN is set (length: ${#CLOUDFLARE_API_TOKEN})"
          fi
          
          if [ -z "${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" ]; then
            echo "❌ CLOUDFLARE_ACCOUNT_ID is not set"
          else
            echo "✅ CLOUDFLARE_ACCOUNT_ID is set: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}"
          fi
          
          if [ -z "${{ secrets.CLOUDFLARE_PROJECT_NAME }}" ]; then
            echo "❌ CLOUDFLARE_PROJECT_NAME is not set"
          else
            echo "✅ CLOUDFLARE_PROJECT_NAME is set: ${{ secrets.CLOUDFLARE_PROJECT_NAME }}"
          fi
        env:
          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
 
      - name: Test Cloudflare API connection
        env:
          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          CLOUDFLARE_PROJECT_NAME: ${{ secrets.CLOUDFLARE_PROJECT_NAME }}
        run: |
          echo "🌐 Testing Cloudflare API connection..."
          
          # Test API token validity
          response=$(curl -s -w "%{http_code}" -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify" \
            -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \
            -H "Content-Type: application/json")
          
          http_code=${response: -3}
          body=${response%???}
          
          echo "HTTP Status: $http_code"
          echo "Response: $body"
          
          if [ "$http_code" != "200" ]; then
            echo "❌ API token verification failed"
            exit 1
          else
            echo "✅ API token is valid"
          fi
          
          # Test Pages project access
          echo "📄 Testing Pages project access..."
          project_response=$(curl -s -w "%{http_code}" -X GET "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/pages/projects/${CLOUDFLARE_PROJECT_NAME}" \
            -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \
            -H "Content-Type: application/json")
          
          project_http_code=${project_response: -3}
          project_body=${project_response%???}
          
          echo "Project HTTP Status: $project_http_code"
          echo "Project Response: $project_body"
          
          if [ "$project_http_code" != "200" ]; then
            echo "❌ Pages project access failed"
            echo "🔧 Possible issues:"
            echo "   - Project name '${CLOUDFLARE_PROJECT_NAME}' doesn't exist"
            echo "   - API token doesn't have Cloudflare Pages:Edit permission"
            echo "   - Account ID is incorrect"
          else
            echo "✅ Pages project access successful"
          fi
 
      - name: Verify build output before deployment
        run: |
          echo "🔍 Verifying build output in public directory..."
          echo "================================================"
          
          # Check if public directory exists
          if [ ! -d "public" ]; then
            echo "❌ ERROR: public directory does not exist!"
            exit 1
          fi
          
          # Show directory structure
          echo "📁 Public directory structure:"
          find public -type f -name "*" | head -20
          
          # Count files
          total_files=$(find public -type f | wc -l)
          html_files=$(find public -name "*.html" | wc -l)
          
          echo ""
          echo "📊 File count summary:"
          echo "   Total files: $total_files"
          echo "   HTML files: $html_files"
          
          # Show file sizes
          echo ""
          echo "📏 File sizes:"
          du -h public/* 2>/dev/null || echo "No files in public directory"
          
          # Show first few lines of each HTML file
          echo ""
          echo "📄 HTML file previews:"
          for html in public/*.html; do
            if [ -f "$html" ]; then
              echo "--- $(basename "$html") ---"
              head -5 "$html"
              echo ""
            fi
          done
          
          # Check if index.html exists
          if [ -f "public/index.html" ]; then
            echo "✅ index.html exists"
            index_size=$(wc -c < public/index.html)
            echo "   Size: $index_size bytes"
          else
            echo "❌ WARNING: index.html missing"
          fi
          
          # Validate that we have content to deploy
          if [ "$total_files" -eq 0 ]; then
            echo "❌ ERROR: No files to deploy!"
            exit 1
          fi
          
          echo ""
          echo "✅ Build verification completed. Ready for deployment."
 
      - name: Deploy to Cloudflare Pages
        uses: cloudflare/pages-action@v1
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          projectName: ${{ secrets.CLOUDFLARE_PROJECT_NAME }}
          directory: public
          wranglerVersion: '3'
 
      - name: Post-deployment verification
        env:
          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          CLOUDFLARE_PROJECT_NAME: ${{ secrets.CLOUDFLARE_PROJECT_NAME }}
        run: |
          echo "🎉 Deployment completed!"
          echo ""
          echo "📊 Deployment summary:"
          echo "   Project: ${CLOUDFLARE_PROJECT_NAME}"
          echo "   Account: ${CLOUDFLARE_ACCOUNT_ID}"
          echo ""
          
          # Get project info to show the URL
          project_info=$(curl -s -X GET "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/pages/projects/${CLOUDFLARE_PROJECT_NAME}" \
            -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \
            -H "Content-Type: application/json")
          

デプロイ

GitHub CLIを使って手動でワークフローを実行:

# GitHub CLIをインストール(未インストールの場合)
brew install gh
 
# 認証(初回のみ)
gh auth login
 
# ワークフローを実行
gh workflow run "Deploy Slides to Cloudflare Pages" --ref main

スライドをデプロイする機会がそうそうないので手動デプロイのみにしています。

ワークフローの状況確認

# 実行中のワークフローを確認
gh run list --workflow=deploy-slides
 
# 特定のランの詳細を確認
gh run view <run-id>

🎨 カスタムテーマ

slides/themes/custom.css でテーマをカスタマイズできます。詳細はMarpのドキュメントを参照してください。

🔍 トラブルシューティング

デプロイが失敗する場合

  1. GitHub Actionsのログを確認
  2. Cloudflare Pagesの設定を確認
  3. API トークンの権限を確認

Marpエラーが出る場合

  • ファイルの先頭に正しいfront matterが設定されているか確認
  • テーマファイルが正しく配置されているか確認

📚 参考リンク