GitHub Pages

Webホスティング静的サイトJekyllGitHub ActionsCI/CDオープンソースドキュメント

Webホスティングプラットフォーム

GitHub Pages

概要

GitHub PagesはGitHubリポジトリから直接ホスティングできる静的サイト向けサービスです。Jekyll統合により、マークダウンからHTMLへの自動変換とブログ機能を提供し、オープンソースプロジェクトのドキュメントサイトやポートフォリオサイトで広く利用されています。GitHubとの緊密な統合により継続的に安定した需要を維持しています。

詳細

2008年にローンチされたGitHub Pagesは、GitHubの代表的な機能の一つとして多くの開発者に愛用されています。特にオープンソースプロジェクトのドキュメント、個人のポートフォリオサイト、技術ブログで人気。Jekyll(Ruby製静的サイトジェネレーター)との統合により、Markdownファイルを自動的にHTMLに変換し、テーマやプラグインの豊富なエコシステムを活用可能。2025年からはGitHub Actionsを使用したビルドプロセスに移行し、より柔軟なカスタマイゼーションと新しいJekyllバージョンの利用が可能になりました。

メリット・デメリット

メリット

  • 完全無料: パブリックリポジトリでは無制限に利用可能
  • GitHubとの統合: リポジトリ管理とホスティングが一体化
  • Jekyll統合: Markdownからの自動HTML生成
  • カスタムドメイン対応: 独自ドメインの設定が可能
  • HTTPS対応: 自動SSL証明書発行
  • GitHub Actions: 高度なビルドプロセスのカスタマイズ
  • 豊富なテーマ: 数多くの美しいJekyllテーマが利用可能

デメリット

  • 静的サイトのみ: サーバーサイド処理は不可
  • ビルド制限: 月10GBのバンドウィズ制限
  • Jekyll依存: 他のSSGを使用する際はGitHub Actions必須
  • パフォーマンス: 他の専用CDNと比べて速度が劣る場合有り
  • 機能制限: 高度な機能やプラグインに制約

参考ページ

書き方の例

基本的なセットアップとプロジェクト設定

# GitHub CLIでのリポジトリ作成
gh repo create my-website --public --clone
cd my-website

# Jekyll サイトの初期化
jekyll new . --force
bundle install

# GitHub Pagesの有効化(Settings > Pages)
# Source: Deploy from a branch または GitHub Actions

# 初期コミット
git add .
git commit -m "Initial Jekyll site"
git push origin main
# _config.yml - Jekyll基本設定
title: My Awesome Website
description: A great site built with Jekyll and hosted on GitHub Pages
baseurl: "" # リポジトリ名(例: "/my-website")
url: "https://username.github.io"

# GitHub Pages設定
github_username: your-username
repository: your-username/my-website

# Jekyll設定
markdown: kramdown
highlighter: rouge
theme: minima

# プラグイン設定
plugins:
  - jekyll-feed
  - jekyll-sitemap
  - jekyll-seo-tag
  - jekyll-redirect-from

# 除外ファイル
exclude:
  - Gemfile
  - Gemfile.lock
  - node_modules
  - vendor/bundle/
  - vendor/cache/
  - vendor/gems/
  - vendor/ruby/

静的サイトデプロイ

# .github/workflows/jekyll.yml - Jekyll GitHub Actions
name: Build and deploy Jekyll site to GitHub Pages

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

permissions:
  contents: read
  pages: write
  id-token: write

concurrency:
  group: "pages"
  cancel-in-progress: false

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        
      - name: Setup Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: '3.1'
          bundler-cache: true
          cache-version: 0
          
      - name: Setup Pages
        id: pages
        uses: actions/configure-pages@v4
        
      - name: Build with Jekyll
        run: bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}"
        env:
          JEKYLL_ENV: production
          
      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: ./_site

  deploy:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    needs: build
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4

フレームワーク統合(Next.js、React、Vue)

# .github/workflows/nextjs.yml - Next.js GitHub Actions
name: Deploy Next.js site to Pages

on:
  push:
    branches: ["main"]
  workflow_dispatch:

permissions:
  contents: read
  pages: write
  id-token: write

concurrency:
  group: "pages"
  cancel-in-progress: false

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        
      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: npm
          
      - name: Setup Pages
        uses: actions/configure-pages@v4
        with:
          static_site_generator: next
          
      - name: Restore cache
        uses: actions/cache@v4
        with:
          path: |
            .next/cache
          key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}
          
      - name: Install dependencies
        run: npm ci
        
      - name: Build with Next.js
        run: npm run build
        
      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: ./out

  deploy:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    needs: build
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4
// next.config.js - GitHub Pages対応
/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'export',
  trailingSlash: true,
  images: {
    unoptimized: true
  },
  basePath: process.env.NODE_ENV === 'production' ? '/repository-name' : '',
  assetPrefix: process.env.NODE_ENV === 'production' ? '/repository-name/' : '',
};

module.exports = nextConfig;

カスタムドメインとSSL

# カスタムドメインの設定
echo "example.com" > CNAME
git add CNAME
git commit -m "Add custom domain"
git push origin main

# DNS設定(CNAMEレコード)
# example.com -> username.github.io
# www.example.com -> username.github.io
# _config.yml - カスタムドメイン設定
url: "https://example.com"
baseurl: ""

# HTTPS強制
enforce_ssl: true

# カスタムドメイン用プラグイン
plugins:
  - jekyll-redirect-from

# リダイレクト設定
redirect_from:
  - /old-page/
  - /another-old-page/
<!-- 404.html - カスタム404ページ -->
---
layout: default
permalink: /404.html
---

<div class="error-page">
  <h1>404 - Page Not Found</h1>
  <p>The page you're looking for doesn't exist.</p>
  <a href="{{ site.baseurl }}/" class="btn">Go Home</a>
</div>

<script>
  // SPA用のクライアントサイドルーティング
  (function() {
    var path = window.location.pathname;
    var base = '{{ site.baseurl }}';
    
    // GitHub Pagesでのルーティング処理
    if (path !== base + '/404.html') {
      window.location.replace(base + '/#' + path);
    }
  })();
</script>

サーバーレスファンクションとAPI

# .github/workflows/api-simulation.yml - 疑似API作成
name: Generate API Data

on:
  push:
    branches: [ main ]
  schedule:
    - cron: '0 0 * * *'  # 毎日実行

jobs:
  generate-data:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '18'
          
      - name: Generate API data
        run: |
          mkdir -p api
          
          # ユーザーデータの生成
          cat > api/users.json << 'EOF'
          {
            "users": [
              {
                "id": 1,
                "name": "John Doe",
                "email": "[email protected]",
                "created_at": "${{ steps.date.outputs.date }}"
              }
            ],
            "total": 1,
            "generated_at": "${{ steps.date.outputs.date }}"
          }
          EOF
          
          # 統計データの生成
          node -e "
            const stats = {
              page_views: Math.floor(Math.random() * 10000),
              unique_visitors: Math.floor(Math.random() * 1000),
              last_updated: new Date().toISOString()
            };
            require('fs').writeFileSync('api/stats.json', JSON.stringify(stats, null, 2));
          "
      
      - name: Commit and push
        run: |
          git config --local user.email "[email protected]"
          git config --local user.name "GitHub Action"
          git add api/
          git diff --staged --quiet || git commit -m "Update API data"
          git push
// _includes/api-client.js - 静的APIクライアント
class StaticAPI {
  constructor(baseUrl) {
    this.baseUrl = baseUrl || '';
  }
  
  async get(endpoint) {
    try {
      const response = await fetch(`${this.baseUrl}/api/${endpoint}.json`);
      if (!response.ok) throw new Error(`HTTP ${response.status}`);
      return await response.json();
    } catch (error) {
      console.error('API Error:', error);
      return null;
    }
  }
  
  // 検索機能(静的データベース)
  async search(query, type = 'posts') {
    const data = await this.get(type);
    if (!data) return [];
    
    const searchTerm = query.toLowerCase();
    return data.filter(item => 
      item.title.toLowerCase().includes(searchTerm) ||
      item.content.toLowerCase().includes(searchTerm)
    );
  }
}

// 使用例
const api = new StaticAPI('{{ site.baseurl }}');

// ページ読み込み時のデータフェッチ
document.addEventListener('DOMContentLoaded', async () => {
  const users = await api.get('users');
  const stats = await api.get('stats');
  
  // データの表示
  if (users) {
    document.getElementById('user-count').textContent = users.total;
  }
  
  if (stats) {
    document.getElementById('page-views').textContent = stats.page_views;
  }
});

CI/CDと本番最適化

# .github/workflows/optimization.yml - 最適化ワークフロー
name: Build and Optimize

on:
  push:
    branches: [ main ]

jobs:
  build-and-optimize:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '18'
          cache: 'npm'
          
      - name: Install dependencies
        run: npm ci
        
      - name: Build Jekyll site
        run: |
          bundle install
          bundle exec jekyll build
          
      - name: Optimize images
        run: |
          npm install -g imagemin-cli imagemin-webp imagemin-mozjpeg imagemin-pngquant
          imagemin "_site/**/*.{jpg,jpeg,png}" --out-dir="_site" --plugin=mozjpeg --plugin=pngquant
          
      - name: Minify HTML/CSS/JS
        run: |
          npm install -g html-minifier clean-css-cli uglify-js
          
          # HTML minification
          find _site -name "*.html" -exec html-minifier --collapse-whitespace --remove-comments {} \;
          
          # CSS minification
          find _site -name "*.css" -exec cleancss -o {} {} \;
          
          # JS minification
          find _site -name "*.js" -exec uglifyjs {} -o {} \;
          
      - name: Generate PWA files
        run: |
          cat > _site/manifest.json << 'EOF'
          {
            "name": "${{ github.repository }}",
            "short_name": "Site",
            "start_url": "{{ site.baseurl }}/",
            "display": "standalone",
            "background_color": "#ffffff",
            "theme_color": "#000000"
          }
          EOF
          
      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: ./_site
# Gemfile - Jekyll プラグイン設定
source "https://rubygems.org"

gem "jekyll", "~> 4.3.0"
gem "github-pages", group: :jekyll_plugins

group :jekyll_plugins do
  gem "jekyll-feed"
  gem "jekyll-sitemap"
  gem "jekyll-seo-tag"
  gem "jekyll-redirect-from"
  gem "jekyll-paginate"
  gem "jekyll-gist"
  gem "jekyll-github-metadata"
  gem "jekyll-include-cache"
  gem "jekyll-compress-images"
  gem "jekyll-minifier"
end

# Performance monitoring
gem "jekyll-analytics"

# Development
group :development do
  gem "webrick"
end
# 開発・運用コマンド集
# ローカル開発
bundle exec jekyll serve --livereload --port 4000

# 本番ビルド
JEKYLL_ENV=production bundle exec jekyll build

# 依存関係更新
bundle update

# GitHub Pages互換性チェック
github-pages health-check

# サイト分析
bundle exec jekyll doctor

# プレビュー(GitHub Pages環境)
act -W .github/workflows/pages.yml