seo-analytics について
Webサイト運営者や分析担当者向けの SEO 分析スキルです。Google PageSpeed Insights API を使って性能と SEO を調査し、API キーを用いてコンソール概要と操作可能な HTML レポートを生成します。
主な機能
- 01性能、アクセシビリティ、ベストプラクティス、SEO の分析
- 02Core Web Vitals の評価
- 03モバイルとデスクトップの指標表示
- 04操作可能な HTML グラフレポートの生成
- 05Google PageSpeed Insights API キーが必要
活用シーン
- 01Webページの速度確認
- 02Core Web Vitals レポートの作成
- 03SEO と Lighthouse 指標の確認
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ rebyteai-template/ rebyte-skills --skill seo-analyticsインストール後、Claude Code で/seo-analyticsを入力して読み込まれたか確認します。
同梱ファイルanalyze.tsREADME.md
詳細
SEO 分析スキル
Google PageSpeed Insights API を使用して、あらゆるウェブサイトの包括的な SEO およびパフォーマンス分析レポートを生成します。
使用するタイミング
- 「https://example.com の SEO を分析して」
- 「ウェブサイトのパフォーマンスを確認して」
- 「Core Web Vitals レポートを生成して……」
- 「……のページ速度スコアは?」
- 「……の SEO 監査をして」
API キーの要件
重要:このスキルには Google PageSpeed Insights API キーが必要です。ユーザーから API キーが提供されていない場合は、先に必ずキーを求めてください。
API キーが提供されていない場合
ユーザーが API キーを提供せずに SEO 分析を依頼した場合は、次のように応答してください。
SEO 分析を実行するには、Google PageSpeed Insights API キーが必要です。
API キーの取得方法(約 2 分):
- Google Cloud Console にアクセスします
- 新しいプロジェクトを作成するか、既存のプロジェクトを選択します
- APIs & Services > Library に移動します
- "PageSpeed Insights API" を検索し、Enable をクリックします
- APIs & Services > Credentials に移動します
- Create Credentials > API Key をクリックします
- 新しい API キーをコピーします
キーを取得したら、提供してください。分析を実行します。
API キーの使用方法
API キーは次の 2 つの方法で指定できます。
- コマンドライン引数として指定:
npx ts-node analyze.ts <URL> <API_KEY> - 環境変数として指定:
PAGESPEED_API_KEY
クイックスタート
User: "Analyze https://eng0.ai"
# If no API key provided, ask user for one first
# With API key:
Output:
- ./seo-report.html (interactive report with charts)
- Console summary with key metrics
実行方法
ステップ 1:分析スクリプトを実行する
npx ts-node analyze.ts https://example.com YOUR_API_KEY
または、環境変数を使う場合:
PAGESPEED_API_KEY=your_key npx ts-node analyze.ts https://example.com
ステップ 2:インラインで実行する(別の方法)
現在のディレクトリに analyze.ts を作成して実行します。
// analyze.ts - SEO Analysis Script
const url = process.argv[2];
const apiKey = process.argv[3] || process.env.PAGESPEED_API_KEY;
if (!url) {
console.error('Usage: npx ts-node analyze.ts <URL> [API_KEY]');
console.error('Or set PAGESPEED_API_KEY environment variable');
process.exit(1);
}
interface AuditResult {
title?: string;
description?: string;
score?: number | null;
displayValue?: string;
numericValue?: number;
}
interface PageSpeedResult {
lighthouseResult: {
categories: {
performance: { score: number };
accessibility: { score: number };
'best-practices': { score: number };
seo: { score: number };
};
audits: {
[key: string]: AuditResult;
};
};
}
async function analyzeUrl(targetUrl: string, strategy: 'mobile' | 'desktop'): Promise<PageSpeedResult> {
let apiUrl = `https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=${encodeURIComponent(targetUrl)}&strategy=${strategy}&category=performance&category=accessibility&category=best-practices&category=seo`;
if (apiKey) {
apiUrl += `&key=${apiKey}`;
}
console.log(`Analyzing ${strategy}...`);
const response = await fetch(apiUrl);
if (!response.ok) {
throw new Error(`API error: ${response.status} ${response.statusText}`);
}
return response.json() as Promise<PageSpeedResult>;
}
function getScoreColor(score: number): string {
if (score >= 90) return '#0cce6b';
if (score >= 50) return '#ffa400';
return '#ff4e42';
}
function generateReport(mobile: PageSpeedResult, desktop: PageSpeedResult, targetUrl: string): string {
const m = mobile.lighthouseResult;
const d = desktop.lighthouseResult;
const mobileScores = {
performance: Math.round(m.categories.performance.score * 100),
accessibility: Math.round(m.categories.accessibility.score * 100),
bestPractices: Math.round(m.categories['best-practices'].score * 100),
seo: Math.round(m.categories.seo.score * 100),
};
const desktopScores = {
performance: Math.round(d.categories.performance.score * 100),
accessibility: Math.round(d.categories.accessibility.score * 100),
bestPractices: Math.round(d.categories['best-practices'].score * 100),
seo: Math.round(d.categories.seo.score * 100),
};
const webVitals = {
mobile: {
lcp: m.audits['largest-contentful-paint'],
tbt: m.audits['total-blocking-time'],
cls: m.audits['cumulative-layout-shift'],
fcp: m.audits['first-contentful-paint'],
si: m.audits['speed-index'],
tti: m.audits['interactive'],
},
desktop: {
lcp: d.audits['largest-contentful-paint'],
tbt: d.audits['total-blocking-time'],
cls: d.audits['cumulative-layout-shift'],
fcp: d.audits['first-contentful-paint'],
si: d.audits['speed-index'],
tti: d.audits['interactive'],
},
};
// Extract top opportunities
const opportunities: string[] = [];
for (const [key, audit] of Object.entries(m.audits)) {
if (audit.score !== undefined && audit.score < 0.9 && audit.title && audit.description) {
opportunities.push(`<li><strong>${audit.title}</strong>: ${audit.description.split('.')[0]}.</li>`);
}
}
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SEO Report - ${targetUrl}</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; line-height: 1.6; }
.container { max-width: 1200px; margin: 0 auto; padding: 2rem; }
h1 { font-size: 1.8rem; margin-bottom: 0.5rem; }
.url { color: #666; font-size: 0.9rem; margin-bottom: 2rem; word-break: break-all; }
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 1.5rem; margin-bottom: 2rem; }
.card { background: white; border-radius: 8px; padding: 1.5rem; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.card h2 { font-size: 1.1rem; margin-bottom: 1rem; color: #444; }
.scores { display: flex; justify-content: space-around; text-align: center; }
.score-item { display: flex; flex-direction: column; align-items: center; }
.score-circle { width: 60px; height: 60px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 1.2rem; font-weight: bold; color: white; margin-bottom: 0.5rem; }
.score-label { font-size: 0.75rem; color: #666; }
.vitals-table { width: 100%; border-collapse: collapse; }
.vitals-table th, .vitals-table td { padding: 0.75rem; text-align: left; border-bottom: 1px solid #eee; }
.vitals-table th { font-weight: 500; color: #666; font-size: 0.85rem; }
.good { color: #0cce6b; }
.needs-improvement { color: #ffa400; }
.poor { color: #ff4e42; }
.chart-container { position: relative; height: 300px; }
.opportunities { list-style: none; }
.opportunities li { padding: 0.75rem 0; border-bottom: 1px solid #eee; font-size: 0.9rem; }
.opportunities li:last-child { border-bottom: none; }
.timestamp { text-align: center; color: #999; font-size: 0.8rem; margin-top: 2rem; }
</style>
</head>
<body>
<div class="container">
<h1>SEO Analysis Report</h1>
<p class="url">${targetUrl}</p>
<div class="grid">
<div class="card">
<h2>Mobile Scores</h2>
<div class="scores">
<div class="score-item">
<div class="score-circle" style="background: ${getScoreColor(mobileScores.performance)}">${mobileScores.performance}</div>
<span class="score-label">Performance</span>
</div>
<div class="score-item">
<div class="score-circle" style="background: ${getScoreColor(mobileScores.accessibility)}">${mobileScores.accessibility}</div>
<span class="score-label">Accessibility</span>
</div>
<div class="score-item">
<div class="score-circle" style="background: ${getScoreColor(mobileScores.bestPractices)}">${mobileScores.bestPractices}</div>
<span class="score-label">Best Practices</span>
</div>
<div class="score-item">
<div class="score-circle" style="background: ${getScoreColor(mobileScores.seo)}">${mobileScores.seo}</div>
<span class="score-label">SEO</span>
</div>
</div>
</div>
<div class="card">
<h2>Desktop Scores</h2>
<div class="scores">
<div class="score-item">
<div class="score-circle" style="background: ${getScoreColor(desktopScores.performance)}">${desktopScores.performance}</div>
<span class="score-label">Performance</span>
</div>
<div class="score-item">
<div class="score-circle" style="background: ${getScoreColor(desktopScores.accessibility)}">${desktopScores.accessibility}</div>
<span class="score-label">Accessibility</span>
</div>
<div class="score-item">
<div class="score-circle" style="background: ${getScoreColor(desktopScores.bestPractices)}">${desktopScores.bestPractices}</div>
<span class="score-label">Best Practices</span>
</div>
<div class="score-item">
<div class="score-circle" style="background: ${getScoreColor(desktopScores.seo)}">${desktopScores.seo}</div>
<span class="score-label">SEO</span>
</div>
</div>
</div>
</div>
<div class="grid">
<div class="card">
<h2>Core Web Vitals - Mobile</h2>
<table class="vitals-table">
<tr><th>Metric</th><th>Value</th><th>Status</th></tr>
<tr>
<td>Largest Contentful Paint (LCP)</td>
<td>${webVitals.mobile.lcp?.displayValue || 'N/A'}</td>
<td class="${(webVitals.mobile.lcp?.numericValue || 0) <= 2500 ? 'good' : (webVitals.mobile.lcp?.numericValue || 0) <= 4000 ? 'needs-improvement' : 'poor'}">${(webVitals.mobile.lcp?.numericValue || 0) <= 2500 ? 'Good' : (webVitals.mobile.lcp?.numericValue || 0) <= 4000 ? 'Needs Work' : 'Poor'}</td>
</tr>
<tr>
<td>Total Blocking Time (TBT)</td>
<td>${webVitals.mobile.tbt?.displayValue || 'N/A'}</td>
<td class="${(webVitals.mobile.tbt?.numericValue || 0) <= 200 ? 'good' : (webVitals.mobile.tbt?.numericValue || 0) <= 600 ? 'needs-improvement' : 'poor'}">${(webVitals.mobile.tbt?.numericValue || 0) <= 200 ? 'Good' : (webVitals.mobile.tbt?.numericValue || 0) <= 600 ? 'Needs Work' : 'Poor'}</td>
</tr>
<tr>
<td>Cumulative Layout Shift (CLS)</td>
<td>${webVitals.mobile.cls?.displayValue || 'N/A'}</td>
<td class="${(webVitals.mobile.cls?.numericValue || 0) <= 0.1 ? 'good' : (webVitals.mobile.cls?.numericValue || 0) <= 0.25 ? 'needs-improvement' : 'poor'}">${(webVitals.mobile.cls?.numericValue || 0) <= 0.1 ? 'Good' : (webVitals.mobile.cls?.numericValue || 0) <= 0.25 ? 'Needs Work' : 'Poor'}</td>
</tr>
<tr>
<td>First Contentful Paint (FCP)</td>
<td>${webVitals.mobile.fcp?.displayValue || 'N/A'}</td>
<td class="${(webVitals.mobile.fcp?.numericValue || 0) <= 1800 ? 'good' : (webVitals.mobile.fcp?.numericValue || 0) <= 3000 ? 'needs-improvement' : 'poor'}">${(webVitals.mobile.fcp?.numericValue || 0) <= 1800 ? 'Good' : (webVitals.mobile.fcp?.numericValue || 0) <= 3000 ? 'Needs Work' : 'Poor'}</td>
</tr>
<tr>
<td>Speed Index</td>
<td>${webVitals.mobile.si?.displayValue || 'N/A'}</td>
<td class="${(webVitals.mobile.si?.numericValue || 0) <= 3400 ? 'good' : (webVitals.mobile.si?.numericValue || 0) <= 5800 ? 'needs-improvement' : 'poor'}">${(webVitals.mobile.si?.numericValue || 0) <= 3400 ? 'Good' : (webVitals.mobile.si?.numericValue || 0) <= 5800 ? 'Needs Work' : 'Poor'}</td>
</tr>
<tr>
<td>Time to Interactive (TTI)</td>
<td>${webVitals.mobile.tti?.displayValue || 'N/A'}</td>
<td class="${(webVitals.mobile.tti?.numericValue || 0) <= 3800 ? 'good' : (webVitals.mobile.tti?.numericValue || 0) <= 7300 ? 'needs-improvement' : 'poor'}">${(webVitals.mobile.tti?.numericValue || 0) <= 3800 ? 'Good' : (webVitals.mobile.tti?.numericValue || 0) <= 7300 ? 'Needs Work' : 'Poor'}</td>
</tr>
</table>
</div>
<div class="card">
<h2>Score Comparison</h2>
<div class="chart-container">
<canvas id="comparisonChart"></canvas>
</div>
</div>
</div>
<div class="card">
<h2>Optimization Opportunities</h2>
<ul class="opportunities">
${opportunities.slice(0, 10).join('\n ') || '<li>No significant issues found. Great job!</li>'}
</ul>
</div>
<p class="timestamp">Generated on ${new Date().toLocaleString()}</p>
</div>
<script>
new Chart(document.getElementById('comparisonChart'), {
type: 'radar',
data: {
labels: ['Performance', 'Accessibility', 'Best Practices', 'SEO'],
datasets: [
{
label: 'Mobile',
data: [${mobileScores.performance}, ${mobileScores.accessibility}, ${mobileScores.bestPractices}, ${mobileScores.seo}],
borderColor: '#4285f4',
backgroundColor: 'rgba(66, 133, 244, 0.2)',
},
{
label: 'Desktop',
data: [${desktopScores.performance}, ${desktopScores.accessibility}, ${desktopScores.bestPractices}, ${desktopScores.seo}],
borderColor: '#34a853',
backgroundColor: 'rgba(52, 168, 83, 0.2)',
}
]
},
options: {
scales: { r: { beginAtZero: true, max: 100 } },
plugins: { legend: { position: 'bottom' } }
}
});
</script>
</body>
</html>`;
}
async function main() {
try {
console.log('Starting SEO analysis for:', url);
console.log('');
// Sequential requests to avoid rate limiting
const mobile = await analyzeUrl(url, 'mobile');
const desktop = await analyzeUrl(url, 'desktop');
const report = generateReport(mobile, desktop, url);
const fs = await import('fs');
const outputPath = './seo-report.html';
fs.writeFileSync(outputPath, report);
// Print summary
const m = mobile.lighthouseResult;
const d = desktop.lighthouseResult;
console.log('');
console.log('=== SEO Analysis Complete ===');
console.log('');
console.log('Mobile Scores:');
console.log(` Performance: ${Math.round(m.categories.performance.score * 100)}/100`);
console.log(` Accessibility: ${Math.round(m.categories.accessibility.score * 100)}/100`);
console.log(` Best Practices: ${Math.round(m.categories['best-practices'].score * 100)}/100`);
console.log(` SEO: ${Math.round(m.categories.seo.score * 100)}/100`);
console.log('');
console.log('Desktop Scores:');
console.log(` Performance: ${Math.round(d.categories.performance.score * 100)}/100`);
console.log(` Accessibility: ${Math.round(d.categories.accessibility.score * 100)}/100`);
console.log(` Best Practices: ${Math.round(d.categories['best-practices'].score * 100)}/100`);
console.log(` SEO: ${Math.round(d.categories.seo.score * 100)}/100`);
console.log('');
console.log('Core Web Vitals (Mobile):');
console.log(` LCP: ${m.audits['largest-contentful-paint']?.displayValue || 'N/A'}`);
console.log(` TBT: ${m.audits['total-blocking-time']?.displayValue || 'N/A'}`);
console.log(` CLS: ${m.audits['cumulative-layout-shift']?.displayValue || 'N/A'}`);
console.log('');
console.log(`Report saved to: ${outputPath}`);
console.log('Open the HTML file in a browser to view the interactive report.');
} catch (error) {
console.error('Error:', error);
process.exit(1);
}
}
main();
次のコマンドで実行します。
npx ts-node analyze.ts https://eng0.ai YOUR_API_KEY
出力
このスキルでは、次の内容が生成されます。
- コンソールの概要 - すべてのスコアの概要をすばやく確認できます
- seo-report.html - 次の内容を含むインタラクティブな HTML レポート:
- モバイルとデスクトップのスコアカード
- ステータスインジケーター付き Core Web Vitals 表
- モバイルとデスクトップを比較するレーダーチャート
- 優先度の高い最適化の機会
指標の説明
Lighthouse スコア(0~100)
- パフォーマンス:ページの読み込み速度と操作性
- アクセシビリティ:障害のあるユーザーにとってのページの利用しやすさ
- ベストプラクティス:最新のウェブ開発標準
- SEO:検索エンジン最適化の基本
Core Web Vitals
| 指標 | 良好 | 要改善 | 不良 |
|---|---|---|---|
| LCP(Largest Contentful Paint) | < 2.5s | 2.5-4s | > 4s |
| TBT(Total Blocking Time) | < 200ms | 200-600ms | > 600ms |
| CLS(Cumulative Layout Shift) | < 0.1 | 0.1-0.25 | > 0.25 |
API の詳細
Google PageSpeed Insights API v5 を使用します。
- 無料:基本的な使用に API キーは不要です
- レート制限:100 秒あたり約 25 リクエスト
- データ:Lighthouse の実際の監査結果
出力例
=== SEO Analysis Complete ===
Mobile Scores:
Performance: 89/100
Accessibility: 95/100
Best Practices: 100/100
SEO: 91/100
Core Web Vitals (Mobile):
LCP: 1.8 s
TBT: 150 ms
CLS: 0.02
Report saved to: ./seo-report.html
コメント
Markdown 対応