VitePressブログの収益化に向けてやった8つの対策
公開日: 2026-03-17
前回の記事でSEO対策を実装しました。しかし、ブログのURLをClaudeに共有して相談してみたところ、そもそもこのサイトがGoogle検索にインデックスされていないことが判明しました。サイトマップやOGPを設定しただけでは不十分で、Search Consoleへの登録が必要だったのです。
前提環境
当ブログは VitePress + AWS(S3 / CloudFront / Route53)で構成し、インフラを Terraform で管理しています。構築手順はこちらの記事を参照してください。
実施した施策は以下の8点です。
- Google Search Console に登録する
- Google Analytics(GA4)を導入する
- 広告審査に必要なページを作る
- OGP画像を用意する
- タグページ・関連記事で回遊率を上げる
- CloudFrontキャッシュを最適化する
- 画像を最適化する
- Clean URLsでURL形式を統一する
1. Google Search Console に登録する
これが最優先です。 サイトマップを作っても、Search Consoleに登録してサイトマップを送信しなければ、Googleはサイトの存在を認識してくれません。
ドメインプロパティの追加
Google Search Console にアクセスすると、プロパティタイプの選択画面が表示されます。

左側の 「ドメイン」 を選択し、自分のドメイン(例: garookie.com)を入力して「続行」をクリックします。右側の「URLプレフィックス」方式もありますが、ドメイン方式ならwww有無やhttp/httpsの違いを全てカバーできるのでおすすめです。
DNS に TXT レコードを追加する
「続行」をクリックすると、DNSレコードによるドメイン所有権の確認画面が表示されます。

画面の指示に従い、ステップ3に表示される google-site-verification=... のTXTレコードをDNSに追加します。当ブログではRoute53をTerraformで管理しているので、route53.tf にこの値を設定しました。
resource "aws_route53_record" "google_site_verification" {
zone_id = aws_route53_zone.blog.zone_id
name = var.domain_name
type = "TXT"
ttl = 300
records = ["google-site-verification=xxxxxxxxxx"] # ステップ3の値を貼り付け
}terraform apply でレコードを作成したあと、画面右下の「確認」ボタンを押せば所有権の確認が完了します。
DNS反映の待ち時間
DNSレコードの反映には数分〜最大24時間かかることがあります。すぐに確認できなければ1日待ってから再度試しましょう。
サイトマップの送信
所有権が確認できたら、左メニューの「サイトマップ」から https://garookie.com/sitemap.xml を送信します。これでGoogleのクローラーがサイト内のページを巡回し始めます。
なお、サイトマップを送信してから実際にGoogle検索にインデックスされるまでは 通常数日〜数週間 かかります。すぐに検索結果に表示されなくても焦らず待ちましょう。
確認方法
Search Consoleの「URL検査」に自サイトのURLを入力し、「URLはGoogleに登録されています」と表示されればインデックス完了です。
Bing Webmaster Tools にも登録する
Google以外の検索エンジンにもインデックスされるよう、Bing Webmaster Tools にも登録しました。Google Search Consoleの設定をインポートできるので、追加の手間はほとんどかかりません。
2. Google Analytics(GA4)を導入する
サイトのアクセス状況を把握するためにGA4を導入します。広告サービスの審査ではアクセス解析の導入も求められるため、早めに入れておきました。
Google Analytics でプロパティを作成し、測定IDを取得したら、config.js の head にスクリプトタグを追加します。
head: [
// ...既存の設定
['script', { async: '', src: 'https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX' }],
['script', {}, "window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments)}gtag('js',new Date());gtag('config','G-XXXXXXXXXX');"]
],G-XXXXXXXXXX は実際の測定IDに置き換えてください。ついでに author メタタグも追加しておきました。
['meta', { name: 'author', content: '著者名' }],確認方法
デプロイ後にGA4の「リアルタイム」レポートを開き、自分でサイトにアクセスして計測されるか確認しましょう。
3. 広告審査に必要なページを作る
広告サービスの審査に通るには、最低限 プライバシーポリシー と お問い合わせページ が必要です。
プライバシーポリシー
docs/privacy.md を作成し、以下の内容を記載しました。
- 運営者情報(ハンドルネーム)
- Google Analyticsの使用について(Cookie利用、データ収集範囲、オプトアウト方法)
- 広告配信について(広告配信サービス導入予定の旨)
- 免責事項
- お問い合わせ先
- 制定日
お問い合わせページ
docs/contact.md にGoogleフォームをiframeで埋め込みました。フォーム自体はGoogle Formsで作成し、右上の三点リーダから「HTMLを埋め込む」を選択してiframeのURLを取得します。
<iframe
src="https://docs.google.com/forms/d/e/YOUR_FORM_ID/viewform?embedded=true"
width="100%" height="800" frameborder="0"
style="border: none; max-width: 640px;">
読み込んでいます...
</iframe>フッターにリンクを配置
これらのページはメインナビゲーションに並べるとごちゃつくので、フッターに配置しました。VitePressでは themeConfig.footer で設定できます。
themeConfig: {
footer: {
message: '<a href="/privacy">プライバシーポリシー</a> | <a href="/contact">お問い合わせ</a>',
copyright: '© 2026 サイト名'
}
}確認方法
npm run docs:dev でローカルサーバーを起動し、各ページのフッターにリンクが表示されること、リンク先が正しく表示されることを確認しましょう。
4. OGP画像を用意する
OGP画像は、SNSでURLを共有した時に表示されるサムネイル画像です。前回OGPタグは設定していましたが、肝心の画像を用意していませんでした。
画像の作成
サイト名とサブタイトルをテキストで配置したシンプルなデザインで、サイズはOGP推奨の 1200x630px です。Canvaなどのデザインツールや、Node.jsの sharp ライブラリで作成できます。
作成した画像は docs/public/og-image.png に配置しましょう。public/ に置いたファイルはビルド時にそのまま出力されるので、https://garookie.com/og-image.png でアクセスできるようになります。
メタタグの追加
config.js の transformHead に og:image と twitter:image を追加し、twitter:card を summary_large_image に変更しました。
// OGP image
head.push(['meta', { property: 'og:image', content: `${hostname}/og-image.png` }])
// Twitter Card(summaryからsummary_large_imageに変更)
head.push(['meta', { name: 'twitter:card', content: 'summary_large_image' }])
head.push(['meta', { name: 'twitter:image', content: `${hostname}/og-image.png` }])これでXやFacebookでURLを共有した際に、大きな画像付きのカードが表示されるようになります。
確認方法
Facebook Sharing Debugger や X Card Validator にURLを入力し、画像が正しく表示されるか確認できます。
5. タグページ・関連記事で回遊率を上げる
広告収入を増やすにはページビュー数が重要です。訪問者がサイト内の他の記事にも興味を持てるよう、タグページと関連記事機能を追加しました。
タグ一覧ページ
docs/tags.md を作成しました。posts.data.js の返却データに tags フィールドを追加し、タグボタンのクリックで記事をフィルタリングできるようにしています。既存の記事一覧ページ(posts/index.md)のカテゴリフィルタと同じパターンを応用しました。
関連記事コンポーネント
記事末尾に、同じタグを持つ記事を「関連記事」として表示するコンポーネント RelatedPosts.vue を作成しました。
ポイントは、VitePressのデータローダー(.data.js)がVueコンポーネントから直接importできない点です。そのため、ビルド時に posts-meta.json を出力し、コンポーネントから fetch する方式を採用しました。
config.js の buildEnd フックで、RSS生成と同じタイミングで JSON を出力します。
async buildEnd(siteConfig) {
// ...RSS生成の後に追加
const postsMeta = posts
.filter(p => p.frontmatter.date)
.sort((a, b) => new Date(b.frontmatter.date) - new Date(a.frontmatter.date))
.map(post => ({
title: post.frontmatter.title,
url: post.url,
tags: post.frontmatter.tags || [],
date: post.frontmatter.date
}))
writeFileSync(path.join(siteConfig.outDir, 'posts-meta.json'), JSON.stringify(postsMeta))
}コンポーネント側は onMounted で fetch し、タグの一致数でソートして最大5件を表示します。
広告スロット
広告サービス導入後にすぐ広告を表示できるよう、AdSlot.vue コンポーネントを用意しました。
VitePressのデフォルトテーマには、レイアウトの各所にコンポーネントを差し込める「スロット」が用意されています。今回は以下の3箇所に広告枠を配置しました。
doc-before— 記事タイトル下doc-after— 記事末尾(ViewCounter・関連記事の後)aside-outline-after— サイドバー
記事ページ(frontmatter.date が存在するページ)でのみ表示されます。現時点では空のdivですが、広告コードを差し込むだけで広告が表示される仕組みです。
// docs/.vitepress/theme/index.js
export default {
extends: DefaultTheme,
Layout() {
return h(DefaultTheme.Layout, null, {
'doc-before': () => h(AdSlot, { position: 'title-below' }),
'doc-after': () => [
h(ViewCounter),
h(RelatedPosts),
h(AdSlot, { position: 'after-article' })
],
'aside-outline-after': () => h(AdSlot, { position: 'sidebar' }),
})
},
}確認方法
npm run docs:build && npm run docs:preview でビルド後のサイトを確認し、タグページのフィルタリングと記事末尾の関連記事が正しく動作するか確認しましょう。
6. CloudFrontキャッシュを最適化する
VitePressのビルド出力では、CSS/JSファイルはハッシュ付きのファイル名(例: app.a1b2c3d4.js)で assets/ ディレクトリに配置されます。ファイル内容が変わればファイル名自体が変わるため、古いキャッシュが残る心配がなく、長期キャッシュを安全に設定できます。
Terraform でファイルタイプ別のキャッシュを設定
cloudfront.tf に ordered_cache_behavior を追加しました。
# CSS/JS(ハッシュ付きファイル名)→ 1年キャッシュ
ordered_cache_behavior {
path_pattern = "assets/*"
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "S3-${var.bucket_name}"
viewer_protocol_policy = "redirect-to-https"
compress = true
forwarded_values {
query_string = false
cookies { forward = "none" }
}
min_ttl = 0
default_ttl = 31536000 # 1 year
max_ttl = 31536000
}
# 画像 → 7日キャッシュ
ordered_cache_behavior {
path_pattern = "images/*"
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "S3-${var.bucket_name}"
viewer_protocol_policy = "redirect-to-https"
compress = true
forwarded_values {
query_string = false
cookies { forward = "none" }
}
min_ttl = 0
default_ttl = 604800 # 7 days
max_ttl = 2592000 # 30 days
}HTMLはデフォルトの default_ttl = 3600(1時間)のままです。
デプロイスクリプトの分割
GitHub Actionsの deploy.yml も、ファイルタイプ別に Cache-Control ヘッダーを設定するよう分割しました。
# HTML等(assets/imagesを除外)
- name: Sync HTML and other files to S3
run: |
aws s3 sync docs/.vitepress/dist/ "s3://${S3_BUCKET_NAME}/" \
--delete \
--cache-control "public, max-age=3600" \
--exclude "assets/*" \
--exclude "images/*"
# 静的アセット(長期キャッシュ)
- name: Sync static assets to S3 (long cache)
run: |
aws s3 sync docs/.vitepress/dist/assets/ "s3://${S3_BUCKET_NAME}/assets/" \
--cache-control "public, max-age=31536000, immutable"
# 画像(中期キャッシュ)
- name: Sync images to S3 (medium cache)
run: |
aws s3 sync docs/.vitepress/dist/images/ "s3://${S3_BUCKET_NAME}/images/" \
--cache-control "public, max-age=604800"CloudFront側の ordered_cache_behavior と S3 の Cache-Control ヘッダーを揃えることで、ブラウザキャッシュとCDNキャッシュの両方が最適に機能します。
確認方法
デプロイ後、ブラウザのDevToolsのNetworkタブでレスポンスヘッダーを確認します。assets/ 配下のファイルに cache-control: public, max-age=31536000, immutable が設定されていればOKです。
7. 画像を最適化する
遅延読み込み
VitePress 1.x には画像の遅延読み込み機能が組み込まれています。config.js に1行追加するだけで、全ての <img> タグに loading="lazy" が付与されます。
markdown: {
image: {
lazyLoading: true
}
}WebP変換スクリプト
画像ファイルのサイズ削減のため、sharp を使ったWebP変換スクリプトを作成しました。
npm install -D sharpscripts/convert-images.js を作成し、docs/public/images/ 内のPNG/JPGファイルをWebPに変換します。主な機能は以下の通りです。
- 対象フォーマット:
.png,.jpg,.jpeg - 品質設定: 80(WebPのquality)
- ファイルの更新日時を比較し、既に最新のWebPファイルが存在する場合はスキップ
- 変換結果の統計情報(変換数・スキップ数・合計)を出力
import sharp from 'sharp'
// 各画像ファイルに対して
await sharp(srcPath).webp({ quality: 80 }).toFile(webpPath)package.json に実行スクリプトを追加して、npm run convert-images で実行できるようにしました。
実際に当ブログの画像で試したところ、PNGからWebPへの変換で約73%のファイルサイズ削減(292KB → 80KB)を達成しました。
確認方法
npm run convert-images を実行し、docs/public/images/ に .webp ファイルが生成されていることを確認しましょう。Lighthouseの「Performance」セクションでも画像フォーマットの最適化状況を確認できます。
8. Clean URLsでURL形式を統一する
施策1〜7を実施してしばらくすると、Google Search Consoleから**「ページにリダイレクトがあります」**という警告メールが届きました。調査したところ、サイトマップのURLとcanonical URLの形式が不一致になっていたことが原因でした。
問題の原因
VitePressはデフォルトで cleanUrls: false(URLに.html拡張子を含む)の設定になっています。一方、前回の記事で設定した transformHead のcanonical URL生成ロジックは .html を含まない形式で出力していました。
サイトマップ: https://garookie.com/about.html ← .htmlあり
canonical: https://garookie.com/about ← .htmlなしこの不一致により、Googleは同じページに対して2つの異なるURLを認識し、一方をリダイレクト扱いとしてインデックス登録を見送っていたのです。
cleanUrls: true を有効にする
config.js に cleanUrls: true を追加するだけで、サイトマップ・内部リンク・canonical URLがすべて .html なしの形式に統一されます。
export default () => {
return withMermaid(defineConfig({
title: siteName,
description: siteDescription,
lang: 'ja',
cleanUrls: true,
// ...
}))
}当ブログではCloudFront Functionが /about のような拡張子なしURLを /about.html に内部書き換えする設定になっているため、インフラ側の変更は不要でした。
確認方法
npm run docs:build 後に docs/.vitepress/dist/sitemap.xml を開き、すべてのURLが .html なしになっていることを確認しましょう。
まとめ
今回実装した8つの施策とその効果をまとめます。
| 施策 | 効果 |
|---|---|
| Search Console登録 | Googleにインデックスされる(必須) |
| GA4導入 | アクセス状況の把握・広告審査の評価ポイント |
| プライバシーポリシー・問い合わせ | 広告審査の必須要件 |
| OGP画像 | SNS共有時のクリック率向上 |
| タグ・関連記事 | サイト内回遊率の向上 |
| キャッシュ最適化 | ページ表示速度の向上 |
| 画像最適化 | ページ表示速度の向上 |
| Clean URLs | URL形式の統一でインデックス登録の阻害を解消 |