🆕 New Guides Added: - GitHub Actions complete guide (CI/CD automation) - GitHub Security comprehensive guide (enterprise security) - GitHub Pages detailed guide (website publishing) 🔗 Navigation Improvements: - Updated index.md with all 7 guides - Added cross-navigation between all guides - Enhanced learning paths for different user types - Comprehensive learning flow diagrams 📚 Content Highlights: - Jenkins/CircleCI migration strategies - Enterprise security best practices - WordPress to GitHub Pages migration - Advanced automation workflows - Complete external tool replacements 🎯 User Experience: - Role-based learning recommendations - Consistent navigation structure - Progressive learning paths - Cross-reference linking 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
52 KiB
52 KiB
| layout | title | description |
|---|---|---|
| default | GitHub Pages完全ガイド | 高機能Webサイト構築とGitHub Pages完全活用法 |
🌐 GitHub Pages - 高機能Webサイト・ドキュメント公開
GitHub Pagesを活用して、WordPress・Netlify・Vercel等の外部ホスティングサービスに依存しない、統合されたWebサイト・ドキュメント公開環境を構築する完全ガイド。静的サイトから動的機能まで、企業レベルのWeb公開を実現します。
🎯 学習目標
- GitHub Pages の全機能理解と戦略的活用
- Jekyll・Hugo・Next.js等の静的サイトジェネレーター完全活用
- カスタムドメイン・SSL・CDN設定による本格運用
- 外部ホスティングサービスからの移行戦略
- 高度なWebサイト自動化・運用最適化
📚 目次
1. GitHub Pages 概要
🏗️ GitHub Pages アーキテクチャ
デプロイメント戦略比較
graph TB
A[GitHub Pages デプロイメント戦略]
A --> B[Classic Pages]
A --> C[GitHub Actions]
B --> B1[直接ブランチ]
B --> B2[docs/ フォルダ]
C --> C1[カスタムビルド]
C --> C2[複数言語対応]
C --> C3[高度な前処理]
style C fill:#e1f5fe
style C1 fill:#e8f5e8
style C2 fill:#e8f5e8
style C3 fill:#e8f5e8
機能比較マトリックス
### GitHub Pages vs 外部ホスティング比較
| 機能 | GitHub Pages | Netlify | Vercel | WordPress.com | 備考 |
|------|--------------|---------|--------|---------------|------|
| **基本ホスティング** | ✅ 無料 | ✅ 無料枠 | ✅ 無料枠 | ⚠️ 有料 | GitHubは無制限 |
| **カスタムドメイン** | ✅ | ✅ | ✅ | ✅ | 全て対応 |
| **SSL証明書** | ✅ 自動 | ✅ 自動 | ✅ 自動 | ✅ 自動 | Let's Encrypt |
| **CDN配信** | ✅ | ✅ | ✅ | ✅ | 全世界配信 |
| **ビルド統合** | ✅ Actions | ✅ | ✅ | ❌ | GitHubが最も柔軟 |
| **Git統合** | ✅ ネイティブ | ✅ | ✅ | ⚠️ 部分的 | GitHubが最も深い統合 |
| **コスト(月額)** | $0 | $0-19 | $0-20 | $4-25 | GitHubが最もコスト効率 |
| **帯域制限** | 100GB/月 | 100GB/月 | 100GB/月 | 無制限 | 個人・小規模には十分 |
| **ストレージ** | 1GB | 無制限 | 無制限 | 3-200GB | 静的サイトには十分 |
🚀 GitHub Pages 設定戦略
レポジトリタイプ別設定
# GitHub Pages 設定パターン
## パターン1: ユーザー/組織サイト
Repository: username.github.io
URL: https://username.github.io
Branch: main (ルートディレクトリ)
Use Case: 個人ポートフォリオ、企業メインサイト
## パターン2: プロジェクトサイト
Repository: any-repo-name
URL: https://username.github.io/repo-name
Branch: main, gh-pages, または docs/
Use Case: プロジェクトドキュメント、製品サイト
## パターン3: カスタムドメイン
Repository: any-repo-name
URL: https://custom-domain.com
Branch: 任意
Custom Domain: CNAME設定
Use Case: 企業サイト、ブランドサイト
2. 静的サイトジェネレーター活用
🎨 Jekyll 高度活用
企業レベルJekyllサイト
# _config.yml - 高度なJekyll設定
title: "Enterprise Documentation Hub"
description: "Complete technical documentation and API reference"
url: "https://docs.company.com"
baseurl: ""
# 高度な設定
timezone: Asia/Tokyo
encoding: utf-8
permalink: /:categories/:year/:month/:day/:title/
# プラグイン設定
plugins:
- jekyll-feed
- jekyll-sitemap
- jekyll-seo-tag
- jekyll-redirect-from
- jekyll-archives
- jekyll-paginate-v2
- jekyll-compress-images
- jekyll-minifier
# SEO設定
seo:
name: "Company Name"
type: "Organization"
links:
- "https://twitter.com/company"
- "https://linkedin.com/company/company"
# パフォーマンス最適化
compress_html:
clippings: all
comments: all
endings: all
ignore:
envs: [development]
# 多言語対応
languages: ["en", "ja", "zh"]
default_lang: "en"
exclude_from_localizations: ["assets", "admin"]
# Collections設定
collections:
api:
output: true
permalink: /:collection/:name/
tutorials:
output: true
permalink: /:collection/:categories/:title/
case_studies:
output: true
permalink: /:collection/:year/:month/:title/
# デフォルト設定
defaults:
- scope:
path: ""
type: "posts"
values:
layout: "post"
author: "Tech Team"
categories: ["blog"]
- scope:
path: ""
type: "api"
values:
layout: "api"
sidebar: "api"
- scope:
path: ""
type: "tutorials"
values:
layout: "tutorial"
sidebar: "tutorials"
# ビルド設定
safe: true
incremental: true
keep_files: [".git", ".svn"]
markdown: kramdown
highlighter: rouge
# Kramdown設定
kramdown:
input: GFM
syntax_highlighter: rouge
syntax_highlighter_opts:
css_class: 'highlight'
span:
line_numbers: false
block:
line_numbers: true
# Sass設定
sass:
style: compressed
sass_dir: _sass
高機能Jekyllテーマ構築
<!-- _layouts/default.html -->
<!DOCTYPE html>
<html lang="{{ page.lang | default: site.lang | default: 'en' }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- SEO メタタグ -->
{% seo %}
<!-- パフォーマンス最適化 -->
<link rel="preload" href="{{ '/assets/css/main.css' | relative_url }}" as="style">
<link rel="preload" href="{{ '/assets/js/main.js' | relative_url }}" as="script">
<!-- CSS -->
<link rel="stylesheet" href="{{ '/assets/css/main.css' | relative_url }}">
<!-- Progressive Web App -->
<link rel="manifest" href="{{ '/manifest.json' | relative_url }}">
<meta name="theme-color" content="#007bff">
<!-- Analytics -->
{% if site.google_analytics %}
<!-- Google Analytics 4 -->
<script async src="https://www.googletagmanager.com/gtag/js?id={{ site.google_analytics }}"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '{{ site.google_analytics }}');
</script>
{% endif %}
</head>
<body class="{{ page.layout }}{% if page.class %} {{ page.class }}{% endif %}">
<!-- Skip to content -->
<a class="skip-link" href="#main-content">Skip to main content</a>
<!-- Header -->
{% include header.html %}
<!-- Main content -->
<main id="main-content" class="main-content" role="main">
<div class="container">
{% if page.sidebar %}
<div class="content-with-sidebar">
<aside class="sidebar">
{% include sidebar/{{ page.sidebar }}.html %}
</aside>
<div class="content">
{{ content }}
</div>
</div>
{% else %}
{{ content }}
{% endif %}
</div>
</main>
<!-- Footer -->
{% include footer.html %}
<!-- JavaScript -->
<script src="{{ '/assets/js/main.js' | relative_url }}" defer></script>
<!-- Service Worker -->
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('{{ '/sw.js' | relative_url }}');
}
</script>
</body>
</html>
⚛️ Next.js静的エクスポート
Next.js + GitHub Pages設定
// next.config.js
const isProd = process.env.NODE_ENV === 'production';
const repoName = 'your-repo-name';
/** @type {import('next').NextConfig} */
const nextConfig = {
// 静的エクスポート設定
output: 'export',
// GitHub Pages用のパス設定
basePath: isProd ? `/${repoName}` : '',
assetPrefix: isProd ? `/${repoName}/` : '',
// 画像最適化(静的エクスポート用)
images: {
unoptimized: true,
},
// トレイリングスラッシュ
trailingSlash: true,
// Strict Mode
reactStrictMode: true,
// パフォーマンス最適化
swcMinify: true,
// 実験的機能
experimental: {
appDir: true,
},
// ヘッダー設定
async headers() {
return [
{
source: '/:path*',
headers: [
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'X-Frame-Options',
value: 'DENY',
},
{
key: 'X-XSS-Protection',
value: '1; mode=block',
},
],
},
];
},
};
module.exports = nextConfig;
Next.js デプロイワークフロー
# .github/workflows/deploy-nextjs.yml
name: Deploy Next.js 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:
name: Build Next.js
runs-on: ubuntu-latest
steps:
- name: Checkout
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 with Next.js
run: |
npm run build
env:
NODE_ENV: production
- name: Upload artifact
uses: actions/upload-pages-artifact@v2
with:
path: ./out
deploy:
name: Deploy to GitHub Pages
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
if: github.ref == 'refs/heads/main'
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v3
🦌 Hugo高速サイト
Hugo設定・テーマカスタマイズ
# config.yaml
baseURL: 'https://username.github.io/repo-name'
languageCode: 'ja'
title: 'Enterprise Hugo Site'
theme: 'custom-theme'
# 多言語設定
defaultContentLanguage: 'ja'
languages:
ja:
title: '企業サイト'
weight: 1
params:
description: '高性能な企業ウェブサイト'
en:
title: 'Enterprise Site'
weight: 2
params:
description: 'High-performance enterprise website'
# メニュー設定
menu:
main:
- name: 'ホーム'
url: '/'
weight: 1
- name: '製品'
url: '/products/'
weight: 2
- name: 'ドキュメント'
url: '/docs/'
weight: 3
- name: 'ブログ'
url: '/blog/'
weight: 4
# パラメータ設定
params:
version: '1.0.0'
description: '高性能静的サイト'
author: 'Tech Team'
email: 'contact@company.com'
# SEO設定
keywords: ['technology', 'enterprise', 'solutions']
# ソーシャルメディア
social:
twitter: 'company'
linkedin: 'company'
github: 'company'
# Analytics
googleAnalytics: 'G-XXXXXXXXXX'
# 機能フラグ
features:
search: true
comments: true
darkMode: true
# マークダウン設定
markup:
goldmark:
renderer:
unsafe: true
highlight:
style: 'github'
lineNos: true
lineNumbersInTable: false
# パフォーマンス最適化
related:
threshold: 80
includeNewer: true
toLower: false
indices:
- name: 'keywords'
weight: 100
- name: 'tags'
weight: 80
# イメージ処理
imaging:
quality: 85
resampleFilter: 'lanczos'
3. 高度なWebサイト構築
🎛️ 動的機能実装
JavaScript API統合
// assets/js/dynamic-content.js
class DynamicContentManager {
constructor() {
this.apiBase = 'https://api.github.com';
this.cache = new Map();
this.init();
}
async init() {
await this.loadGitHubData();
this.setupEventListeners();
this.initializeComponents();
}
async loadGitHubData() {
try {
// リポジトリ情報の取得
const repoData = await this.fetchWithCache('/repos/owner/repo');
this.updateRepoStats(repoData);
// Issue統計の取得
const issues = await this.fetchWithCache('/repos/owner/repo/issues?state=all');
this.updateIssueStats(issues);
// リリース情報の取得
const releases = await this.fetchWithCache('/repos/owner/repo/releases');
this.updateReleaseInfo(releases);
} catch (error) {
console.error('Failed to load GitHub data:', error);
}
}
async fetchWithCache(endpoint) {
if (this.cache.has(endpoint)) {
return this.cache.get(endpoint);
}
const response = await fetch(`${this.apiBase}${endpoint}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
this.cache.set(endpoint, data);
return data;
}
updateRepoStats(repo) {
const elements = {
stars: document.getElementById('github-stars'),
forks: document.getElementById('github-forks'),
issues: document.getElementById('github-issues'),
lastUpdated: document.getElementById('last-updated')
};
if (elements.stars) elements.stars.textContent = repo.stargazers_count.toLocaleString();
if (elements.forks) elements.forks.textContent = repo.forks_count.toLocaleString();
if (elements.issues) elements.issues.textContent = repo.open_issues_count.toLocaleString();
if (elements.lastUpdated) {
elements.lastUpdated.textContent = new Date(repo.updated_at).toLocaleDateString();
}
}
updateIssueStats(issues) {
const openIssues = issues.filter(issue => issue.state === 'open');
const closedIssues = issues.filter(issue => issue.state === 'closed');
const statsContainer = document.getElementById('issue-stats');
if (statsContainer) {
statsContainer.innerHTML = `
<div class="stat-item">
<span class="stat-value">${openIssues.length}</span>
<span class="stat-label">Open Issues</span>
</div>
<div class="stat-item">
<span class="stat-value">${closedIssues.length}</span>
<span class="stat-label">Closed Issues</span>
</div>
`;
}
}
setupEventListeners() {
// 検索機能
const searchInput = document.getElementById('site-search');
if (searchInput) {
let searchTimeout;
searchInput.addEventListener('input', (e) => {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
this.performSearch(e.target.value);
}, 300);
});
}
// ダークモード切り替え
const darkModeToggle = document.getElementById('dark-mode-toggle');
if (darkModeToggle) {
darkModeToggle.addEventListener('click', () => {
this.toggleDarkMode();
});
}
// フィードバックフォーム
const feedbackForm = document.getElementById('feedback-form');
if (feedbackForm) {
feedbackForm.addEventListener('submit', (e) => {
e.preventDefault();
this.submitFeedback(new FormData(feedbackForm));
});
}
}
async performSearch(query) {
if (!query || query.length < 2) {
this.clearSearchResults();
return;
}
try {
// GitHub Search API を使用
const searchResults = await this.fetchWithCache(
`/search/code?q=${encodeURIComponent(query)}+repo:owner/repo`
);
this.displaySearchResults(searchResults.items);
} catch (error) {
console.error('Search failed:', error);
}
}
displaySearchResults(results) {
const resultsContainer = document.getElementById('search-results');
if (!resultsContainer) return;
if (results.length === 0) {
resultsContainer.innerHTML = '<p>No results found</p>';
return;
}
const resultsList = results.map(result => `
<div class="search-result">
<h3><a href="${result.html_url}" target="_blank">${result.name}</a></h3>
<p>${result.path}</p>
<small>Repository: ${result.repository.full_name}</small>
</div>
`).join('');
resultsContainer.innerHTML = resultsList;
}
toggleDarkMode() {
const body = document.body;
const isDark = body.classList.contains('dark-mode');
if (isDark) {
body.classList.remove('dark-mode');
localStorage.setItem('theme', 'light');
} else {
body.classList.add('dark-mode');
localStorage.setItem('theme', 'dark');
}
}
async submitFeedback(formData) {
try {
// GitHub Issue として投稿
const issueData = {
title: `Feedback: ${formData.get('subject')}`,
body: `
## User Feedback
**Page**: ${window.location.href}
**Type**: ${formData.get('type')}
**Subject**: ${formData.get('subject')}
**Message**:
${formData.get('message')}
**User Agent**: ${navigator.userAgent}
**Timestamp**: ${new Date().toISOString()}
`,
labels: ['feedback', 'user-submitted']
};
const response = await fetch(`${this.apiBase}/repos/owner/repo/issues`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `token ${process.env.GITHUB_TOKEN}`
},
body: JSON.stringify(issueData)
});
if (response.ok) {
this.showNotification('Feedback submitted successfully!', 'success');
document.getElementById('feedback-form').reset();
} else {
throw new Error('Failed to submit feedback');
}
} catch (error) {
console.error('Feedback submission failed:', error);
this.showNotification('Failed to submit feedback. Please try again.', 'error');
}
}
showNotification(message, type = 'info') {
const notification = document.createElement('div');
notification.className = `notification notification-${type}`;
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
notification.classList.add('show');
}, 100);
setTimeout(() => {
notification.classList.remove('show');
setTimeout(() => {
document.body.removeChild(notification);
}, 300);
}, 3000);
}
}
// 初期化
document.addEventListener('DOMContentLoaded', () => {
new DynamicContentManager();
});
📱 PWA (Progressive Web App) 対応
Service Worker実装
// sw.js
const CACHE_NAME = 'site-cache-v1';
const STATIC_CACHE = 'static-cache-v1';
const DYNAMIC_CACHE = 'dynamic-cache-v1';
const STATIC_ASSETS = [
'/',
'/assets/css/main.css',
'/assets/js/main.js',
'/assets/images/logo.svg',
'/manifest.json',
'/offline.html'
];
// Install event
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(STATIC_CACHE).then((cache) => {
return cache.addAll(STATIC_ASSETS);
})
);
self.skipWaiting();
});
// Activate event
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames
.filter((cacheName) => {
return cacheName !== STATIC_CACHE && cacheName !== DYNAMIC_CACHE;
})
.map((cacheName) => {
return caches.delete(cacheName);
})
);
})
);
self.clients.claim();
});
// Fetch event
self.addEventListener('fetch', (event) => {
const { request } = event;
// Skip non-GET requests
if (request.method !== 'GET') return;
// Skip external requests
if (!request.url.startsWith(self.location.origin)) return;
event.respondWith(
caches.match(request).then((cachedResponse) => {
if (cachedResponse) {
return cachedResponse;
}
return fetch(request).then((networkResponse) => {
// Clone the response before caching
const responseClone = networkResponse.clone();
// Cache dynamic content
if (request.url.includes('/api/') || request.url.includes('.json')) {
caches.open(DYNAMIC_CACHE).then((cache) => {
cache.put(request, responseClone);
});
}
return networkResponse;
}).catch(() => {
// Return offline page for navigation requests
if (request.mode === 'navigate') {
return caches.match('/offline.html');
}
});
})
);
});
// Background sync
self.addEventListener('sync', (event) => {
if (event.tag === 'background-sync') {
event.waitUntil(
// Handle background synchronization
handleBackgroundSync()
);
}
});
// Push notifications
self.addEventListener('push', (event) => {
const options = {
body: event.data ? event.data.text() : 'New update available!',
icon: '/assets/images/icon-192.png',
badge: '/assets/images/badge.png',
vibrate: [100, 50, 100],
data: {
dateOfArrival: Date.now(),
primaryKey: 1
},
actions: [
{
action: 'explore',
title: 'View Details',
icon: '/assets/images/checkmark.png'
},
{
action: 'close',
title: 'Close',
icon: '/assets/images/xmark.png'
}
]
};
event.waitUntil(
self.registration.showNotification('Site Update', options)
);
});
async function handleBackgroundSync() {
// Implementation for background data sync
try {
const response = await fetch('/api/sync');
const data = await response.json();
// Process synced data
console.log('Background sync completed:', data);
} catch (error) {
console.error('Background sync failed:', error);
}
}
Web App Manifest
{
"name": "Enterprise Documentation Hub",
"short_name": "EnterpriseDocs",
"description": "Complete technical documentation and API reference",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#007bff",
"orientation": "portrait-primary",
"categories": ["productivity", "developer", "documentation"],
"lang": "ja",
"dir": "ltr",
"icons": [
{
"src": "/assets/images/icon-72.png",
"sizes": "72x72",
"type": "image/png",
"purpose": "any"
},
{
"src": "/assets/images/icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/assets/images/icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "/assets/images/icon-maskable.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
"screenshots": [
{
"src": "/assets/images/screenshot-desktop.png",
"sizes": "1280x720",
"type": "image/png",
"form_factor": "wide"
},
{
"src": "/assets/images/screenshot-mobile.png",
"sizes": "390x844",
"type": "image/png",
"form_factor": "narrow"
}
],
"shortcuts": [
{
"name": "API Reference",
"short_name": "API",
"description": "Access API documentation",
"url": "/api/",
"icons": [
{
"src": "/assets/images/api-icon.png",
"sizes": "96x96"
}
]
},
{
"name": "Tutorials",
"short_name": "Learn",
"description": "View tutorials and guides",
"url": "/tutorials/",
"icons": [
{
"src": "/assets/images/tutorial-icon.png",
"sizes": "96x96"
}
]
}
],
"related_applications": [
{
"platform": "web",
"url": "https://docs.company.com"
}
],
"prefer_related_applications": false
}
4. カスタムドメイン・SSL設定
🌐 独自ドメイン設定
DNS設定とCNAME
# DNS設定例(お名前.com、Route 53等)
## Aレコード設定(ルートドメイン用)
@ IN A 185.199.108.153
@ IN A 185.199.109.153
@ IN A 185.199.110.153
@ IN A 185.199.111.153
## AAAAレコード設定(IPv6対応)
@ IN AAAA 2606:50c0:8000::153
@ IN AAAA 2606:50c0:8001::153
@ IN AAAA 2606:50c0:8002::153
@ IN AAAA 2606:50c0:8003::153
## CNAMEレコード設定(サブドメイン用)
www IN CNAME username.github.io.
docs IN CNAME username.github.io.
api IN CNAME username.github.io.
## GitHub Pages検証用
_github-pages-challenge-username IN TXT "verification-token"
GitHub Pages カスタムドメイン設定
# 1. CNAMEファイル作成
echo "docs.company.com" > CNAME
# 2. 設定の確認
curl -H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/username/repo/pages
# 3. カスタムドメイン設定(API経由)
curl -X POST \
-H "Accept: application/vnd.github.v3+json" \
-H "Authorization: token YOUR_TOKEN" \
https://api.github.com/repos/username/repo/pages \
-d '{
"source": {
"branch": "main",
"path": "/"
},
"cname": "docs.company.com"
}'
🔒 SSL・セキュリティ強化
セキュリティヘッダー実装
# .github/workflows/security-headers.yml
name: Add Security Headers
on:
push:
branches: [main]
jobs:
add-security-headers:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Generate _headers file
run: |
cat > _headers << 'EOF'
/*
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(), microphone=(), camera=()
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' https://www.googletagmanager.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com; connect-src 'self' https://api.github.com
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
EOF
- name: Generate security.txt
run: |
mkdir -p .well-known
cat > .well-known/security.txt << 'EOF'
Contact: security@company.com
Expires: 2024-12-31T23:59:59.999Z
Encryption: https://company.com/pgp-key.txt
Acknowledgments: https://company.com/security/hall-of-fame
Policy: https://company.com/security/policy
Canonical: https://company.com/.well-known/security.txt
EOF
- name: Commit security files
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add _headers .well-known/security.txt
git commit -m "Add security headers and security.txt" || exit 0
git push
5. 外部サービスからの移行
🔄 WordPress から GitHub Pages 移行
WordPress エクスポート・変換
# wordpress_to_jekyll.py
import xml.etree.ElementTree as ET
import os
import re
from datetime import datetime
import html
class WordPressToJekyll:
def __init__(self, wordpress_xml, output_dir):
self.wordpress_xml = wordpress_xml
self.output_dir = output_dir
self.posts_dir = os.path.join(output_dir, '_posts')
self.pages_dir = os.path.join(output_dir, '_pages')
# ディレクトリ作成
os.makedirs(self.posts_dir, exist_ok=True)
os.makedirs(self.pages_dir, exist_ok=True)
def convert(self):
"""WordPressエクスポートファイルをJekyll形式に変換"""
tree = ET.parse(self.wordpress_xml)
root = tree.getroot()
# 名前空間の定義
namespaces = {
'wp': 'http://wordpress.org/export/1.2/',
'content': 'http://purl.org/rss/1.0/modules/content/',
'excerpt': 'http://wordpress.org/export/1.2/excerpt/',
'dc': 'http://purl.org/dc/elements/1.1/'
}
items = root.findall('.//item')
for item in items:
post_type = item.find('wp:post_type', namespaces)
status = item.find('wp:status', namespaces)
if post_type is not None and status is not None:
if status.text == 'publish':
if post_type.text == 'post':
self.convert_post(item, namespaces)
elif post_type.text == 'page':
self.convert_page(item, namespaces)
def convert_post(self, item, namespaces):
"""投稿をJekyll形式に変換"""
title = self.get_text(item, 'title')
content = self.get_text(item, 'content:encoded', namespaces)
pub_date = self.get_text(item, 'wp:post_date', namespaces)
slug = self.get_text(item, 'wp:post_name', namespaces)
# カテゴリとタグの取得
categories = []
tags = []
for category in item.findall('category'):
domain = category.get('domain')
if domain == 'category':
categories.append(category.get('nicename'))
elif domain == 'post_tag':
tags.append(category.get('nicename'))
# 日付のパース
try:
date_obj = datetime.strptime(pub_date, '%Y-%m-%d %H:%M:%S')
date_str = date_obj.strftime('%Y-%m-%d')
filename = f"{date_str}-{slug}.md"
except:
filename = f"{slug}.md"
date_str = datetime.now().strftime('%Y-%m-%d')
# Front Matter の作成
front_matter = f"""---
layout: post
title: "{self.escape_yaml(title)}"
date: {date_str}
categories: [{', '.join(f'"{cat}"' for cat in categories)}]
tags: [{', '.join(f'"{tag}"' for tag in tags)}]
author: "WordPress Import"
---
"""
# HTMLコンテンツをMarkdownに変換(簡易版)
markdown_content = self.html_to_markdown(content)
# ファイル出力
with open(os.path.join(self.posts_dir, filename), 'w', encoding='utf-8') as f:
f.write(front_matter + markdown_content)
print(f"Converted post: {filename}")
def convert_page(self, item, namespaces):
"""固定ページをJekyll形式に変換"""
title = self.get_text(item, 'title')
content = self.get_text(item, 'content:encoded', namespaces)
slug = self.get_text(item, 'wp:post_name', namespaces)
front_matter = f"""---
layout: page
title: "{self.escape_yaml(title)}"
permalink: /{slug}/
---
"""
markdown_content = self.html_to_markdown(content)
filename = f"{slug}.md"
with open(os.path.join(self.pages_dir, filename), 'w', encoding='utf-8') as f:
f.write(front_matter + markdown_content)
print(f"Converted page: {filename}")
def get_text(self, element, tag, namespaces=None):
"""要素からテキストを安全に取得"""
if namespaces:
found = element.find(tag, namespaces)
else:
found = element.find(tag)
return found.text if found is not None and found.text else ""
def escape_yaml(self, text):
"""YAML用のエスケープ処理"""
return text.replace('"', '\\"').replace('\n', '\\n')
def html_to_markdown(self, html_content):
"""HTMLを簡易Markdownに変換"""
if not html_content:
return ""
# HTMLエンティティのデコード
content = html.unescape(html_content)
# 基本的なHTML→Markdown変換
conversions = [
(r'<h1[^>]*>(.*?)</h1>', r'# \1'),
(r'<h2[^>]*>(.*?)</h2>', r'## \1'),
(r'<h3[^>]*>(.*?)</h3>', r'### \1'),
(r'<h4[^>]*>(.*?)</h4>', r'#### \1'),
(r'<h5[^>]*>(.*?)</h5>', r'##### \1'),
(r'<h6[^>]*>(.*?)</h6>', r'###### \1'),
(r'<strong[^>]*>(.*?)</strong>', r'**\1**'),
(r'<b[^>]*>(.*?)</b>', r'**\1**'),
(r'<em[^>]*>(.*?)</em>', r'*\1*'),
(r'<i[^>]*>(.*?)</i>', r'*\1*'),
(r'<code[^>]*>(.*?)</code>', r'`\1`'),
(r'<a[^>]*href="([^"]*)"[^>]*>(.*?)</a>', r'[\2](\1)'),
(r'<img[^>]*src="([^"]*)"[^>]*>', r''),
(r'<p[^>]*>(.*?)</p>', r'\1\n\n'),
(r'<br[^>]*/?>', r'\n'),
(r'<ul[^>]*>(.*?)</ul>', r'\1'),
(r'<ol[^>]*>(.*?)</ol>', r'\1'),
(r'<li[^>]*>(.*?)</li>', r'- \1\n'),
(r'<blockquote[^>]*>(.*?)</blockquote>', r'> \1\n'),
]
for pattern, replacement in conversions:
content = re.sub(pattern, replacement, content, flags=re.DOTALL | re.IGNORECASE)
# 残りのHTMLタグを除去
content = re.sub(r'<[^>]+>', '', content)
# 余分な空行を整理
content = re.sub(r'\n\s*\n\s*\n', '\n\n', content)
return content.strip()
# 使用例
if __name__ == "__main__":
converter = WordPressToJekyll('wordpress-export.xml', 'jekyll-site')
converter.convert()
print("WordPress to Jekyll conversion completed!")
📊 移行比較・ROI計算
コスト・機能比較分析
## WordPress.com → GitHub Pages 移行分析
### 📊 年間コスト比較(企業サイト想定)
| 項目 | WordPress.com | GitHub Pages | 節約額 |
|------|---------------|--------------|--------|
| **ホスティング** | $300/年 | $0/年 | $300 |
| **カスタムドメイン** | $18/年 | $12/年 | $6 |
| **SSL証明書** | 含む | $0(無料) | $0 |
| **CDN** | $0-200/年 | $0(含む) | $200 |
| **バックアップ** | $120/年 | $0(Git) | $120 |
| **テーマ・プラグイン** | $200/年 | $0(カスタム) | $200 |
| **メンテナンス** | $600/年 | $100/年 | $500 |
| **セキュリティ** | $180/年 | $0(標準) | $180 |
**年間総コスト**:
- WordPress.com: $1,418/年
- GitHub Pages: $112/年
- **年間節約額: $1,306 (92%削減)**
### 🚀 パフォーマンス比較
| 指標 | WordPress.com | GitHub Pages | 改善 |
|------|---------------|--------------|------|
| **読み込み速度** | 2.5秒 | 0.8秒 | 68%向上 |
| **Lighthouse Score** | 75/100 | 98/100 | 31%向上 |
| **稼働率** | 99.9% | 99.95% | 0.05%向上 |
| **CDN配信** | 地域限定 | 全世界 | グローバル |
| **キャッシュ効率** | 中 | 高 | 大幅改善 |
### ✅ 機能比較
| 機能 | WordPress.com | GitHub Pages | 移行可否 |
|------|---------------|--------------|----------|
| **ブログ機能** | ✅ | ✅ Jekyll | ✅ |
| **CMS管理** | ✅ Web UI | ⚠️ Git/Markdown | 代替可能 |
| **コメント機能** | ✅ | ⚠️ 外部サービス | 代替可能 |
| **検索機能** | ✅ | ⚠️ JavaScript実装 | 実装可能 |
| **フォーム** | ✅ | ⚠️ 外部サービス | 代替可能 |
| **eコマース** | ✅ | ❌ | 別途必要 |
| **マルチユーザー** | ✅ | ⚠️ Git権限管理 | 代替可能 |
| **バックアップ** | ✅ | ✅ Git | 同等以上 |
| **バージョン管理** | ⚠️ | ✅ Git | 大幅改善 |
| **セキュリティ** | ⚠️ | ✅ GitHub | 改善 |
6. 運用最適化・監視
📊 パフォーマンス監視
包括的サイト監視
# .github/workflows/site-monitoring.yml
name: Site Performance Monitoring
on:
schedule:
- cron: '0 */6 * * *' # 6時間毎
workflow_dispatch:
jobs:
lighthouse-audit:
name: Lighthouse Performance Audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install Lighthouse CI
run: npm install -g @lhci/cli
- name: Run Lighthouse
run: |
lhci autorun \
--collect.url=https://username.github.io \
--collect.url=https://username.github.io/docs/ \
--collect.url=https://username.github.io/api/ \
--upload.githubAppToken="${{ secrets.LHCI_GITHUB_APP_TOKEN }}"
env:
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
- name: Generate performance report
run: |
cat > performance-report.md << 'EOF'
# 📊 Performance Monitoring Report
**Generated**: $(date -u)
**Site**: https://username.github.io
## Lighthouse Scores
| Page | Performance | Accessibility | Best Practices | SEO |
|------|-------------|---------------|----------------|-----|
| Home |  |  |  |  |
| Docs |  |  |  |  |
| API |  |  |  |  |
## Key Metrics
- **First Contentful Paint**: 0.8s
- **Largest Contentful Paint**: 1.2s
- **Cumulative Layout Shift**: 0.05
- **Time to Interactive**: 1.5s
## Recommendations
- ✅ Excellent performance scores
- ⚠️ Consider optimizing Best Practices on API page
- 🔍 Monitor mobile performance
---
*Automated report generated by GitHub Actions*
EOF
- name: Upload performance artifacts
uses: actions/upload-artifact@v3
with:
name: performance-report
path: |
performance-report.md
.lighthouseci/
uptime-check:
name: Uptime and Health Check
runs-on: ubuntu-latest
steps:
- name: Check site availability
run: |
urls=(
"https://username.github.io"
"https://username.github.io/docs/"
"https://username.github.io/api/"
)
failed_urls=()
for url in "${urls[@]}"; do
echo "Checking $url..."
if ! curl -f -s -o /dev/null -w "%{http_code}" "$url" | grep -E "^(200|301|302)$" > /dev/null; then
failed_urls+=("$url")
echo "❌ $url is not responding correctly"
else
echo "✅ $url is healthy"
fi
done
if [ ${#failed_urls[@]} -gt 0 ]; then
echo "Failed URLs: ${failed_urls[*]}"
exit 1
fi
- name: SSL certificate check
run: |
echo "Checking SSL certificate validity..."
domain="username.github.io"
expiry_date=$(openssl s_client -servername "$domain" -connect "$domain:443" 2>/dev/null | openssl x509 -noout -dates | grep notAfter | cut -d= -f2)
expiry_timestamp=$(date -d "$expiry_date" +%s)
current_timestamp=$(date +%s)
days_until_expiry=$(( (expiry_timestamp - current_timestamp) / 86400 ))
echo "SSL certificate expires on: $expiry_date"
echo "Days until expiry: $days_until_expiry"
if [ $days_until_expiry -lt 30 ]; then
echo "⚠️ SSL certificate expires in less than 30 days!"
exit 1
else
echo "✅ SSL certificate is valid"
fi
broken-links-check:
name: Broken Links Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install broken-link-checker
run: npm install -g broken-link-checker
- name: Check for broken links
run: |
echo "Checking for broken links..."
blc https://username.github.io \
--recursive \
--ordered \
--exclude-external \
--filter-level 2 \
--get \
--input
- name: Create issue for broken links
if: failure()
uses: actions/github-script@v6
with:
script: |
const title = '🔗 Broken Links Detected';
const body = `
## 🔗 Broken Links Report
The automated link checker has detected broken links on the website.
**Check Time**: ${new Date().toISOString()}
**Workflow**: ${context.workflow}
**Run Number**: ${context.runNumber}
### Action Required
- [ ] Review the workflow logs for specific broken links
- [ ] Fix or remove broken links
- [ ] Update redirects if necessary
### Prevention
- [ ] Add link validation to CI/CD pipeline
- [ ] Regular content audits
- [ ] Monitor external dependencies
**Auto-generated by**: Site Monitoring Workflow
`;
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: title,
body: body,
labels: ['bug', 'website', 'broken-links']
});
security-headers-check:
name: Security Headers Validation
runs-on: ubuntu-latest
steps:
- name: Check security headers
run: |
echo "Checking security headers..."
url="https://username.github.io"
headers=$(curl -s -I "$url")
# 必須セキュリティヘッダーの確認
required_headers=(
"X-Content-Type-Options"
"X-Frame-Options"
"X-XSS-Protection"
"Strict-Transport-Security"
)
missing_headers=()
for header in "${required_headers[@]}"; do
if ! echo "$headers" | grep -i "$header" > /dev/null; then
missing_headers+=("$header")
fi
done
if [ ${#missing_headers[@]} -gt 0 ]; then
echo "❌ Missing security headers: ${missing_headers[*]}"
exit 1
else
echo "✅ All required security headers present"
fi
📈 アクセス解析・最適化
Google Analytics 4 統合
// assets/js/analytics.js
class AdvancedAnalytics {
constructor(gaId) {
this.gaId = gaId;
this.isProduction = window.location.hostname !== 'localhost';
this.init();
}
init() {
if (!this.isProduction || !this.gaId) return;
// Google Analytics 4 の読み込み
this.loadGA4();
// カスタムイベントの設定
this.setupCustomEvents();
// パフォーマンス監視
this.setupPerformanceTracking();
// エラー追跡
this.setupErrorTracking();
}
loadGA4() {
// GA4 スクリプトの動的読み込み
const script = document.createElement('script');
script.async = true;
script.src = `https://www.googletagmanager.com/gtag/js?id=${this.gaId}`;
document.head.appendChild(script);
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', this.gaId, {
// プライバシー設定
anonymize_ip: true,
cookie_flags: 'SameSite=Strict;Secure',
// パフォーマンス監視
custom_map: {
'custom_parameter_1': 'page_load_time',
'custom_parameter_2': 'user_engagement'
}
});
window.gtag = gtag;
}
setupCustomEvents() {
// 外部リンククリック追跡
document.addEventListener('click', (e) => {
const link = e.target.closest('a');
if (link && link.hostname !== window.location.hostname) {
this.trackEvent('click', {
event_category: 'external_link',
event_label: link.href,
transport_type: 'beacon'
});
}
});
// ダウンロード追跡
document.addEventListener('click', (e) => {
const link = e.target.closest('a');
if (link && this.isDownloadLink(link.href)) {
this.trackEvent('file_download', {
file_name: this.getFileName(link.href),
file_extension: this.getFileExtension(link.href),
transport_type: 'beacon'
});
}
});
// スクロール深度追跡
this.setupScrollTracking();
// 検索イベント追跡
const searchInput = document.getElementById('site-search');
if (searchInput) {
searchInput.addEventListener('search', (e) => {
this.trackEvent('search', {
search_term: e.target.value,
event_category: 'site_search'
});
});
}
}
setupScrollTracking() {
let scrollDepths = [25, 50, 75, 90];
let trackedDepths = new Set();
window.addEventListener('scroll', () => {
const scrollPercent = Math.round(
(window.scrollY / (document.body.scrollHeight - window.innerHeight)) * 100
);
scrollDepths.forEach(depth => {
if (scrollPercent >= depth && !trackedDepths.has(depth)) {
trackedDepths.add(depth);
this.trackEvent('scroll', {
event_category: 'engagement',
event_label: `${depth}%`,
value: depth
});
}
});
});
}
setupPerformanceTracking() {
// Core Web Vitals 監視
if ('web-vital' in window) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(this.sendWebVital.bind(this));
getFID(this.sendWebVital.bind(this));
getFCP(this.sendWebVital.bind(this));
getLCP(this.sendWebVital.bind(this));
getTTFB(this.sendWebVital.bind(this));
});
}
// Navigation Timing API
window.addEventListener('load', () => {
setTimeout(() => {
const timing = performance.getEntriesByType('navigation')[0];
if (timing) {
this.trackEvent('page_timing', {
event_category: 'performance',
page_load_time: Math.round(timing.loadEventEnd - timing.fetchStart),
dom_ready_time: Math.round(timing.domContentLoadedEventEnd - timing.fetchStart),
first_byte_time: Math.round(timing.responseStart - timing.fetchStart)
});
}
}, 1000);
});
}
sendWebVital(metric) {
this.trackEvent(metric.name, {
event_category: 'web_vitals',
value: Math.round(metric.value),
metric_id: metric.id,
metric_delta: metric.delta
});
}
setupErrorTracking() {
// JavaScript エラー追跡
window.addEventListener('error', (e) => {
this.trackEvent('exception', {
description: `${e.message} at ${e.filename}:${e.lineno}:${e.colno}`,
fatal: false,
error_type: 'javascript_error'
});
});
// Promise rejection 追跡
window.addEventListener('unhandledrejection', (e) => {
this.trackEvent('exception', {
description: `Unhandled Promise Rejection: ${e.reason}`,
fatal: false,
error_type: 'promise_rejection'
});
});
}
trackEvent(eventName, parameters = {}) {
if (!this.isProduction || typeof gtag === 'undefined') return;
gtag('event', eventName, {
event_timestamp: Date.now(),
page_title: document.title,
page_location: window.location.href,
...parameters
});
}
isDownloadLink(href) {
const downloadExtensions = /\.(pdf|doc|docx|xls|xlsx|ppt|pptx|zip|rar|mp3|mp4|avi|mov)$/i;
return downloadExtensions.test(href);
}
getFileName(href) {
return href.split('/').pop().split('?')[0];
}
getFileExtension(href) {
return href.split('.').pop().split('?')[0].toLowerCase();
}
}
// 初期化
document.addEventListener('DOMContentLoaded', () => {
const gaId = document.querySelector('meta[name="google-analytics"]')?.content;
if (gaId) {
new AdvancedAnalytics(gaId);
}
});
🎓 実践演習
演習1: 企業レベルWebサイト構築
- 多機能サイト - Jekyll/Hugo/Next.js選択と実装
- PWA対応 - Service Worker・Web App Manifest
- パフォーマンス最適化 - 画像最適化・CDN活用
- SEO対策 - 構造化データ・メタタグ最適化
演習2: 外部サービス移行プロジェクト
- 現状分析 - WordPress/他サービスの機能調査
- 移行戦略 - データ移行・機能代替・リスク評価
- 段階移行 - 並行運用・段階的切り替え
- 運用最適化 - 監視・メンテナンス・改善
演習3: 高可用性Webサイト運用
- 監視体制 - パフォーマンス・稼働率・セキュリティ
- 自動化 - デプロイ・バックアップ・復旧
- スケーリング - CDN・キャッシュ戦略
- 継続改善 - A/Bテスト・ユーザーフィードバック
🔗 関連リソース
公式ドキュメント
パフォーマンス・SEOツール
静的サイトジェネレーター
📝 まとめ
GitHub Pages を効果的に活用することで:
✅ 外部サービス不要 - WordPress・Netlify等からの完全移行 ✅ コスト大幅削減 - ホスティング・メンテナンス費用の最適化 ✅ 高性能・高可用性 - CDN・SSL・グローバル配信 ✅ 統合開発環境 - コードとWebサイトの一元管理 ✅ スケーラビリティ - 企業レベルの大規模サイト対応
これで主要なGitHub機能ガイドが完成しました!次はケーススタディとトレーニング資料を作成しましょう。
🔗 関連ガイド
- 前のステップ: GitHub Security編 - セキュアなWebサイト・セキュリティ強化
- 基礎知識: GitHub Actions編 - CI/CDパイプライン構築
- プロジェクト管理: GitHub Projects編 - 統合開発管理
- 開発基礎: Pull Request編 - 協調開発・レビュープロセス
- 総合ガイド: GitHub完全活用ガイド - 全機能の詳細解説
📖 学習フロー
graph LR
A[リポジトリ基礎] --> B[Issues管理]
B --> C[Pull Request]
C --> D[GitHub Projects]
D --> E[GitHub Actions]
E --> F[Security]
F --> G[Pages]
G --> H[完全活用]
style A fill:#f3e5f5
style B fill:#f3e5f5
style C fill:#f3e5f5
style D fill:#f3e5f5
style E fill:#f3e5f5
style F fill:#f3e5f5
style G fill:#e1f5fe
style H fill:#e8f5e8