0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

sitemapの lastmod を全ページ「今日」にしていたら、2ページがGoogleに一度もクロールされていなかった

0
Posted at

Next.js で個人開発のサイトを作っています。無料の計算ツールを集めた ShibaHub というサイトで、いまツールが155個あります。

今日、Search Console を調べていて 「2ページだけ、Googleが存在すら知らない」 という状態を見つけました。原因を追っていったら、自分で書いた sitemap.ts の 1行に行き着いたので、その話を書きます。

初心者の方でも追えるように、用語は都度説明します。


そもそも sitemap.xml とは

「うちのサイトにはこういうページがありますよ」という一覧をGoogleに渡すファイルです。

Next.js(App Router)だと app/sitemap.ts を置くだけで自動生成してくれます。こんな形です。

// app/sitemap.ts
import { MetadataRoute } from "next";

export default function sitemap(): MetadataRoute.Sitemap {
  return [
    { url: "https://example.com/a", lastModified: new Date() },
    { url: "https://example.com/b", lastModified: new Date() },
  ];
}

lastModified「そのページを最後に更新した日」 です。出力されるXMLだとこうなります。

<url>
  <loc>https://example.com/a</loc>
  <lastmod>2026-08-08T09:00:00.000Z</lastmod>
</url>

何が起きていたか

まず、おかしな数字に気づきました。Search Console で、投資系のツール3本が 90日間で表示回数0 だったんです。

「表示回数0」は「検索結果に一度も出ていない」という意味です。順位が低いのとは違います。そもそも土俵に上がっていない状態です。

そこで URL検査API で1本ずつ調べました。Search Console の画面にある「URL検査」を、APIから叩けるやつです。

from google.oauth2 import service_account
from googleapiclient.discovery import build

creds = service_account.Credentials.from_service_account_file(
    "service-account.json",
    scopes=["https://www.googleapis.com/auth/webmasters"],
)
sc = build("searchconsole", "v1", credentials=creds)

r = sc.urlInspection().index().inspect(body={
    "inspectionUrl": "https://shibahub.jp/tools/compound-interest-calculator",
    "siteUrl": "sc-domain:shibahub.jp",
    "languageCode": "ja",
}).execute()

s = r["inspectionResult"]["indexStatusResult"]
print(s.get("verdict"))        # PASS / NEUTRAL / FAIL
print(s.get("coverageState"))  # 「送信して登録されました」など
print(s.get("lastCrawlTime"))  # 最後にクロールされた日時

結果がこれでした。

ページ verdict 最終クロール
複利計算 NEUTRAL(URLが認識されていません) なし
積立シミュレーター NEUTRAL(URLが認識されていません) なし
72の法則 PASS(登録済み) 2026-07-26
利回り計算 PASS(登録済み) 2026-07-25

上2本は、Googleが一度も見に来ていませんでした。

順位が低いのではなく、存在を知られていない。これは全然違う問題です。


犯人探し

まず思いつく原因を順に潰しました。

疑ったこと 結果
ページが404では? 4本とも200
sitemapに入っていない? 4本とも入っている(全322URL)
どこからもリンクされていない? 4本とも同じ場所からリンクされている
作ったばかりで、まだ順番が来ていない? 4本とも2026-07-02作成。5週間経っている

条件がほぼ同じなのに、2本だけ来ていない。 ここで手が止まりました。

で、sitemap を生成しているコードを見返しました。

export default function sitemap(): MetadataRoute.Sitemap {
  const now = new Date();   // ← ビルド時刻

  const tools = TOOLS.map((tool) => ({
    url: `${BASE}/tools/${tool.slug}`,
    lastModified: now,      // ★ 155本ぜんぶ「今日」
    changeFrequency: "monthly",
    priority: 0.9,
  }));
  ...
}

155ページ全部の lastmod に、ビルド時刻を入れていました。

sitemap.ts はビルド時に実行されるので、new Date() は「デプロイした瞬間の時刻」になります。つまり、

デプロイするたびに、1文字も直していないページまで「今日更新しました」とGoogleに申告していた

ということです。私は毎日のようにデプロイしているので、Googleから見ると 「毎日155ページ全部が更新されるサイト」 に見えていたことになります。


なぜこれがまずいのか

Googleは lastmod使います。ただし条件があって、公式のドキュメントにこう書かれています。

Googleは、一貫して正確であることが確認できる場合に lastmod の値を使用します

裏を返すと、当てにならないと判断されたら丸ごと無視されるということです。

無視されると何が起きるか。Googleは自分の判断でクロールの優先順位を決めます。クロールできる量には上限(クロールバジェット)があるので、優先度の低いページはいつまでも順番が回ってきません。

今回の2本は、まさにそれに見えました。

⚠️ これは「証明できた」わけではありません。Googleの内部の判断は見えないので、条件が同じ4本の中で見つかった唯一の差がここだった、というところまでです。ただ、直さない理由もありません。


直し方:git から本当の更新日を拾う

各ページを最後に触ったコミットの日時を git log から取って、JSONに書き出しました。

// scripts/gen-lastmod.mjs
import { execFileSync } from "node:child_process";
import { readdirSync, writeFileSync } from "node:fs";

function lastCommitISO(relPath) {
  try {
    return execFileSync("git", ["log", "-1", "--format=%cI", "--", relPath], {
      encoding: "utf8",
    }).trim() || null;
  } catch {
    return null;
  }
}

const map = {};
for (const entry of readdirSync("app/tools", { withFileTypes: true })) {
  if (!entry.isDirectory()) continue;
  const iso = lastCommitISO(`app/tools/${entry.name}`);
  if (iso) map[`/tools/${entry.name}`] = iso;
}
writeFileSync("lib/lastmod.json", JSON.stringify(map, null, 2));

git log -1 --format=%cI -- <パス> で、そのパスを最後に変更したコミットの日時がISO形式で取れます。

sitemap 側はこれを読むだけです。

import LASTMOD from "@/lib/lastmod.json";

function lastmodOf(path: string, fallback: Date): Date {
  const iso = (LASTMOD as Record<string, string>)[path];
  return iso ? new Date(iso) : fallback;   // ★無ければ従来どおり
}

const tools = TOOLS.map((tool) => ({
  url: `${BASE}/tools/${tool.slug}`,
  lastModified: lastmodOf(`/tools/${tool.slug}`, now),
  changeFrequency: "monthly",
  priority: 0.9,
}));

★ビルド時に git を叩かなかった理由

最初は sitemap.ts の中で直接 git log を呼ぼうと思いました。でもやめました。

Vercelなどのビルド環境は、リポジトリを浅くクローン(shallow clone)することがあるからです。履歴が切り詰められていると git log が期待した日付を返しません。しかもビルドは通ってしまうので、間違った日付が出ていることに気づけません。

なので 手元で生成してコミットする方式にしました。副産物として、「いつ生成したか」がgitの履歴に残ります。

⚠️ 代わりに ページを直したら再生成する手間が増えます。忘れてもビルドは通るので、そこは割り切りです(忘れたら「更新したのに知らせていない」状態になるだけで、壊れはしません)。


結果

本番の sitemap.xml を確認しました。

直す前 直したあと
URL数 322 322
lastmod の日付の種類 1種類 11種類
2026-07-02   281件
2026-07-11     4件
2026-07-12     6件
2026-07-17     7件
2026-07-28     3件
...
2026-08-08    12件

281件が7月2日のままですが、それが事実なので正しい状態です。「全部が今日」より、「ほとんど動いていないが12件だけ今日動いた」のほうが、はるかに情報量があります。

なお、クロールが実際に来るかどうかはこれから数日〜数週間見ないと分かりません。効果が出たかどうかはまだ書けません。


ついでにやったこと

もうひとつ、この4本には 関連ツールへのリンクが1本も無かったので、相互にリンクしました。

インデックス済みの2本(利回り計算・72の法則)から、未クロールの2本(複利計算・積立シミュレーター)へ送る形です。トピックとしても自然な並びなので、ユーザーにとっても悪くないはずです。


まとめ

  • sitemap.tslastModified: new Date() を全ページに入れると、デプロイのたびに全ページが「今日更新」になる
  • Googleは lastmod を使うが、一貫して正確なときだけ。当てにならないと無視される
  • 「表示回数0」は「順位が低い」ではないことがある。 URL検査APIで lastCrawlTime を見れば、クロールされていないのかどうかが分かる
  • git から本当の更新日を拾うのは簡単。ただしビルド環境で git log を叩くのは避ける(浅いクローンで壊れる)

同じように new Date() を全ページに入れている方、けっこう多いんじゃないかと思います。一度自分のサイトの sitemap.xml を開いて、lastmod が全部同じ日付になっていないか見てみてください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?