ブログに新記事を書いたあと、Google Search Console を開いて URL を貼り付けてインデックス申請する——これを毎回やっていました。
デプロイは git push で GitHub Actions が自動化しているのに、インデックス申請だけ手動というのが気になっていました。
調べたところ Google Indexing API を使えば同じ CI パイプラインの中に組み込めることがわかったので実装しました。
対象読者
- 静的ブログを GitHub Actions で自動デプロイしているエンジニア
- Search Console のインデックス申請を自動化したいひと
- Node.js で Google API を使う方法を知りたいひと
前提条件
| 環境 | バージョン |
|---|---|
| Node.js | >= 18 |
| google-auth-library | ^9.0.0 |
| GitHub Actions | 使用中であること |
Google Indexing API とは
Google が提供する REST API で、指定した URL のクロール・インデックスを Google に通知できます。
1POST https://indexing.googleapis.com/v3/urlNotifications:publishリクエストボディ:
1{2 "url": "https://your-site.com/blog/new-post/",3 "type": "URL_UPDATED"4}type には URL_UPDATED(新規・更新)と URL_DELETED(削除)があります。
公式の対象コンテンツと実態
公式ドキュメントでは JobPosting・BroadcastEvent の構造化データを持つページ向けと記載されています。ただし一般ページにも API 自体は動作します(利用規約上のグレーゾーンである点はご認識ください)。
制限: 1日あたり 200 リクエスト(無料)
セットアップ
1. Web Search Indexing API を有効化
Google Cloud Console でプロジェクトを開き、「API とサービス」→「ライブラリ」 で “Web Search Indexing API” を検索して有効化します。
2. サービスアカウントを GSC のオーナーに追加
Google Search Console に Search Console が認識するサービスアカウントのメールアドレスを オーナー として追加します(「ユーザー」では申請できません)。
- Search Console → 設定 → ユーザーと権限
- 「ユーザーを追加」→ サービスアカウントのメールアドレスを入力
- 権限: 「オーナー」 を選択
3. GitHub Secrets に登録
リポジトリの Settings → Secrets and variables → Actions で追加:
| Name | Value |
|---|---|
GSC_INDEXING_SERVICE_ACCOUNT | サービスアカウント JSON の中身をそのままペースト |
実装
スクリプト: scripts/request-indexing.mjs
1import { GoogleAuth } from 'google-auth-library';2import { execSync } from 'child_process';3
4const SITE_URL = process.env.SITE_URL ?? 'https://45395.jp';5const BLOG_CONTENT_DIR = 'src/content/blog/';6const INDEXING_API_ENDPOINT = 'https://indexing.googleapis.com/v3/urlNotifications:publish';7const DRY_RUN = process.env.DRY_RUN === 'true';8
9// git diff で変更されたブログ記事ファイルを取得し、公開 URL に変換する10function getChangedBlogUrls() {11 let diffOutput;12 try {13 diffOutput = execSync('git diff --name-only HEAD~1 HEAD', { encoding: 'utf-8' });14 } catch {15 // HEAD~1 が存在しない場合(初回コミットなど)はスキップ77 collapsed lines
16 console.log('git diff に失敗しました。スキップします。');17 return [];18 }19
20 return diffOutput21 .split('\n')22 .map(f => f.trim())23 .filter(f => f.startsWith(BLOG_CONTENT_DIR) && f.endsWith('.md'))24 .map(f => {25 const slug = f.replace(BLOG_CONTENT_DIR, '').replace(/\.md$/, '');26 return `${SITE_URL}/blog/${slug}/`;27 });28}29
30async function submitUrl(client, url) {31 const response = await client.request({32 url: INDEXING_API_ENDPOINT,33 method: 'POST',34 data: { url, type: 'URL_UPDATED' },35 });36 return response.data;37}38
39async function main() {40 const serviceAccountJson = process.env.GSC_INDEXING_SERVICE_ACCOUNT;41 if (!serviceAccountJson) {42 console.log('GSC_INDEXING_SERVICE_ACCOUNT が未設定のためスキップします。');43 process.exit(0);44 }45
46 const urls = getChangedBlogUrls();47 if (urls.length === 0) {48 console.log('変更されたブログ記事はありません。');49 process.exit(0);50 }51
52 console.log(`インデックス申請対象: ${urls.length} 件`);53 urls.forEach(url => console.log(` ${url}`));54
55 if (DRY_RUN) {56 console.log('\n[DRY RUN] 申請はスキップされました。');57 process.exit(0);58 }59
60 const auth = new GoogleAuth({61 credentials: JSON.parse(serviceAccountJson),62 scopes: ['https://www.googleapis.com/auth/indexing'],63 });64 const client = await auth.getClient();65
66 let successCount = 0;67 let failCount = 0;68
69 for (const url of urls) {70 try {71 const result = await submitUrl(client, url);72 console.log(`✓ ${url} (notifyTime: ${result.urlNotificationMetadata?.latestUpdate?.notifyTime ?? '-'})`);73 successCount++;74 } catch (err) {75 const status = err.response?.status ?? 'unknown';76 const message = err.response?.data?.error?.message ?? err.message;77 console.error(`✗ ${url} [HTTP ${status}]: ${message}`);78 failCount++;79 }80 }81
82 console.log(`\n完了: 成功 ${successCount} 件 / 失敗 ${failCount} 件`);83
84 if (successCount === 0 && failCount > 0) {85 process.exit(1);86 }87}88
89main().catch(err => {90 console.error('予期しないエラー:', err);91 process.exit(1);92});ポイントを解説します。
URL の自動検出
1execSync('git diff --name-only HEAD~1 HEAD', { encoding: 'utf-8' })git diff --name-only HEAD~1 HEAD で直前のコミットとの差分ファイル一覧を取得します。src/content/blog/ 配下の .md ファイルに絞り込み、URL に変換します。
1src/content/blog/gsc-indexing-api.md2→ https://45395.jp/blog/gsc-indexing-api/サービスアカウント認証
google-auth-library の GoogleAuth クラスに認証情報と使用するスコープを渡すだけです。
1const auth = new GoogleAuth({2 credentials: JSON.parse(serviceAccountJson),3 scopes: ['https://www.googleapis.com/auth/indexing'],4});5const client = await auth.getClient();あとは client.request() で API を叩けます。ライブラリがトークン取得・リフレッシュを自動で処理してくれます。
エラーハンドリングの方針
インデックス申請の失敗でデプロイを止めたくないので:
- URL ごとにエラーをキャッチして続行
- 全件失敗した場合のみ
exit(1)で GitHub Actions に失敗を通知 - GitHub Actions 側でも
continue-on-error: trueを設定
1if (successCount === 0 && failCount > 0) {2 process.exit(1);3}DRY RUN モード
DRY_RUN=true を設定すると申請せずに対象 URL を確認できます。ローカルでの動作確認に使います。
1DRY_RUN=true GSC_INDEXING_SERVICE_ACCOUNT='{}' node scripts/request-indexing.mjs2# インデックス申請対象: 1 件3# https://45395.jp/blog/gsc-indexing-api/4# [DRY RUN] 申請はスキップされました。package.json への追加
1{2 "devDependencies": {3 "google-auth-library": "^9.0.0"4 }5}GitHub Actions への組み込み
デプロイステップの直後に追加します。
1- name: Request Google Search indexing2 continue-on-error: true3 env:4 GSC_INDEXING_SERVICE_ACCOUNT: ${{ secrets.GSC_INDEXING_SERVICE_ACCOUNT }}5 run: node scripts/request-indexing.mjscontinue-on-error: true により、インデックス申請が失敗してもデプロイ自体は成功扱いになります。
動作フロー
1git push origin main2 │3 ▼4GitHub Actions5 ├─ pnpm install && pnpm build6 ├─ firebase deploy → Firebase Hosting に公開7 └─ node request-indexing.mjs8 │9 ├─ git diff で変更した .md ファイルを検出10 ├─ URL に変換11 └─ Google Indexing API に POST12 → Google が数時間〜数日でクロール・インデックス注意点
API の制限
| 項目 | 上限 |
|---|---|
| 1日あたりのリクエスト数 | 200 件 |
| バースト上限 | 600 件(短時間) |
1日に大量記事を公開しない個人ブログなら十分です。
Sitemap Ping との併用
今回の実装に加え、サイトマップの更新を Google に通知する “Sitemap Ping” も組み合わせると効果的です。
1- name: Ping Google sitemap2 run: curl "https://www.google.com/ping?sitemap=https://45395.jp/sitemap-index.xml"こちらは API キー不要で、1行追加するだけです。
インデックスされるタイミング
Indexing API はあくまで「クロールのリクエスト」であり、即時インデックスを保証するものではありません。実際には数時間〜数日でインデックスされることが多いです。
まとめ
実装に必要だったのは以下だけでした:
- Google Cloud で Web Search Indexing API を有効化
- サービスアカウントを GSC にオーナーとして登録
- GitHub Secret に JSON を登録
- スクリプト 1 ファイル + ワークフロー 5 行
git push でデプロイとインデックス申請が一度に完了するようになり、手動作業がひとつ減りました。同じ構成のかたの参考になれば幸いです。