mcpField
N
no-outlinks-audit

no-outlinks-audit

github.com/infrasity-labsマーケティング・グロース150129 回インストール17 週間前に更新
リポジトリ

no-outlinks-audit について

no-outlinks-auditは、ブログやコンテンツページの内部リンク切れを調べるSEO監査スキルです。サイトのフレームワークに応じてリンクを検出し、各ページに関連するリンク先、アンカーテキスト、挿入用の文案を提案します。

主な機能

  1. 01サイトマップからコンテンツページを検出
  2. 02フレームワーク別の方法でリンクを検出
  3. 03特定したリンク切れページを検証
  4. 04Ahrefsでページキーワードを調査
  5. 05各ページに3件の内部リンクを提案

活用シーン

  1. 01サイト内リンクがないブログ記事を特定
  2. 02コンテンツ間の関連リンクを計画
  3. 03アンカーテキスト付きの配置と文案を取得

MCP 対応

Ahrefs MCPのsite-explorer-top-pagesツールを使い、ページのキーワードとトラフィックデータを取得します。

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/infrasity-labs/dev-gtm-claude-skills --skill no-outlinks-audit

インストール後、Claude Code で/no-outlinks-auditを入力して読み込まれたか確認します。

同梱ファイルREADME.mdreferences/report-style-reference.html

詳細

スキル:行き止まりページ — 内部リンクの発リンクなし監査

説明

本文コンテンツ内に同じドメインの URL への発リンクがまったくないサイト上のブログ/コンテンツページをすべて検出します。これらのページはリンクエクイティを受け取っても、ほかのページに渡しません。つまり、トピックの権威性をひそかに低下させ、サイト全体のクロール効率を損なう構造上の行き止まりです。

行き止まりページごとに、このスキルは 3 つの提案を生成します。行き止まりページからリンクすべき同じサイト内のページ、正確なアンカーテキスト(リンク先ページの主要キーワード)、行き止まりページ内でリンクを配置する場所、そしてリンクを埋め込んだすぐに貼り付けて使える文脈付きの文です。

このスキルは、Orphan Pages スキルの構造上の逆です。

  • Orphan Pages:被リンクがないページ(どのページからもリンクされていない)
  • Dead-End Pages:発リンクがないページ(ほかのページにリンクしていない)

ツール

  • ステップ 1 では、サイトマップを検出するために bash_tool 経由で curl を使用します
  • ステップ 2 では、フレームワークを検出するために bash_tool 経由で curl を使用します
  • ステップ 3 では、発リンクを検出するために bash_tool 経由で curl を使用します(方法はフレームワークによって異なります)
  • ステップ 5 では、キーワード調査に Ahrefs MCP(site-explorer-top-pages)を使用します

ワークフロー

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ステップ 1 — すべてのページを検出する ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

このステップが必要な理由 確認対象となるブログ/コンテンツページの完全な一覧が必要です。curl を使ったサイトマップの検出は高速かつ無料で、古いクロールキャッシュではなく、実際に稼働中のサイトを反映します。

サブステップ 1A — サイトマップを見つける

有効な XML が返されるまで、順番に実行します。

curl -sL --max-time 15 "https://www.DOMAIN.com/sitemap.xml" | head -50
curl -sL --max-time 15 "https://www.DOMAIN.com/sitemap_index.xml" | head -50
curl -sL --max-time 15 "https://www.DOMAIN.com/robots.txt" | grep -i sitemap

どれも機能しない場合は、ブログのインデックスを直接クロールします。

curl -sL "https://www.DOMAIN.com/blog/" | grep -oP 'href="[^"]*"' | grep '/blog/'

サブステップ 1B — すべてのコンテンツ URL を抽出する

curl -sL "https://www.DOMAIN.com/sitemap.xml" \
  | grep -oP '(?<=<loc>)[^<]+' \
  | grep '/blog/' \
  | grep -v '/blog/$' \
  | grep -v '/page/' \
  | sort -u > /tmp/blog_urls.txt

wc -l /tmp/blog_urls.txt

これを LIST_ALL と呼びます。TOTAL_PAGES = LIST_ALL の長さを記録します。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ステップ 2 — サイトのフレームワークを検出する ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

このステップが必要な理由 発リンクの検出方法は、サイトが HTML をどのようにレンダリングするかによって完全に異なります。間違った方法を実行すると結果がまったく正しくならないため、このステップでそれを防ぎます。

両方の確認を実行します。

# Check response headers
curl -sI "https://www.DOMAIN.com" | grep -i "x-powered-by\|x-nextjs\|x-vercel\|server\|generator"

# Check HTML source for framework signals
curl -sL "https://www.DOMAIN.com" \
  | grep -o 'name="generator"[^>]*\|__NEXT_DATA__\|_next\|__nuxt\|ng-version\|gatsby'

判定表:

検出されたシグナルフレームワーク方法
ヘッダーに x-nextjs-prerender があるNext.jsステップ 3A
HTML に _next または NEXT_DATA があるNext.jsステップ 3A
ヘッダーに x-vercel-cache があり、_next もあるNext.js/Vercelステップ 3A
HTML ソースに gatsby があるGatsby(SSG)ステップ 3B
name="generator" content="WordPress" があるWordPressステップ 3B
x-powered-by: PHP がある従来型 CMSステップ 3B
静的 HTML( タグがある)静的/SSGステップ 3B
curl のレスポンスに タグがない不明な SPAステップ 3C
HTML に __nuxt があるNuxt.jsステップ 3C

重要:全ページの一覧を処理する前に、必ず 3 つのサンプルページで検出方法をテストしてください。リンクがあるとわかっているページからリンクが検出され、内容が少ないとわかっているページからはほとんど、またはまったく検出されないことを確認してください。結果がおかしい場合は、このステップを読み直してください。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ステップ 3 — 各ページの発リンクを検出する ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

ステップ 2 で判定した方法を実行します。

──────────────────────────────────────── ステップ 3A — Next.js サイト(RSC ヘッダー方式) ────────────────────────────────────────

仕組み React Server Components を使用する Next.js では、サーバーでレンダリングされたペイロードのエンドポイントが公開されます。リクエストヘッダーとして RSC: 1 を送信すると、ヘッドレスブラウザーを使わずに、すべてのリンクを含む、レンダリング済みのページデータ全体がシリアライズされた React コンポーネントツリーとしてサーバーから返されます。この方法は、App Router を使用するすべての Next.js サイト(Next.js 13 以降)で機能します。 これは、ISR、SSG、SSR のいずれを使用しているかにかかわらず、Next.js / Vercel 上で動作するすべてのサイトに適した方法です。RSC ペイロードには常に、すべての内部リンクがプレーンテキストのパスとして確認できる状態で、レンダリング済みのコンテンツが含まれます。

FETCH が成功したかどうかを検証する方法 有効な RSC レスポンスは、次の条件を満たします。

  • 常に 10KB を超えています(ほとんどのブログ記事は 50KB〜250KB です)
  • $Sreact.fragment、J:、1:"$ などの React マーカーで始まります
  • ページのレスポンスが 10KB 未満、または先頭 500 文字に $ が含まれていない場合、fetch は失敗しています。そのページには手動確認のフラグを立て、行き止まりとしてマークしてはいけません
import subprocess, re, time

domain = "DOMAIN.com"        # without www, e.g. "infrasity.com"
blog_prefix = "/blog/"       # content prefix, e.g. "/blog/"

with open('/tmp/blog_urls.txt') as f:
    urls = [u.strip() for u in f if u.strip()]

# Asset extensions to exclude — these are not outgoing content links
SKIP_EXT = (
    '.png','.webp','.jpg','.jpeg','.gif','.svg','.ico',
    '.woff2','.woff','.ttf','.eot','.otf',
    '.css','.js','.jsx','.ts','.tsx',
    '.xml','.txt','.pdf','.zip','.mp4','.mp3','.webm'
)

# Path prefixes to exclude — assets, CDN, internal Next.js paths
SKIP_PREFIX = (
    '/PostImages/', '/images/', '/fonts/', '/favicon',
    '/_next/', '/_vercel/', '/static/',
    '/api/', '//cdn.', '//assets.'
)

def get_outgoing_links(url):
    result = subprocess.run([
        'curl', '-sL', '--max-time', '10',
        '-H', 'RSC: 1',
        '-H', 'Next-Router-State-Tree: %5B%22%22%2C%7B%22children%22%3A%5B%22__PAGE__%22%2C%7B%7D%5D%7D%2Cnull%2Cnull%2Ctrue%5D',
        url
    ], capture_output=True, text=True, timeout=15)

    data = result.stdout

    # Validate RSC fetch succeeded
    if len(data) < 10000 or not any(m in data[:500] for m in ['$S', 'J:', '1:"']):
        return None  # Fetch failed — flag for manual review

    # Extract all paths pointing to the same domain
    # Extract both absolute domain links and relative paths starting with /
    # (excluding protocol-relative links starting with //)
    raw = re.findall(
        rf'(?:(?:https?://)?(?:www\\.)?{re.escape(domain)}|(?<!/))/(?!/)([^\\s\"\\\'\\\\),\\]]+)',
        data
    )
    raw = ['/' + r for r in raw]

    clean = set()
    for l in raw:
        # Normalise: strip trailing junk
        l = l.split('\\')[0].split('"')[0].split("'")[0].rstrip('),.]\\/')
        if len(l) < 2:
            continue
        # Exclude assets
        if any(l.lower().endswith(e) for e in SKIP_EXT):
            continue
        if any(l.startswith(p) for p in SKIP_PREFIX):
            continue
        # Exclude self-link
        slug = url.rstrip('/').split('/')[-1]
        if l.rstrip('/') == blog_prefix.rstrip('/') + '/' + slug:
            continue
        clean.add(l)

    return clean

dead_ends = []
has_outlinks = []
failed = []

for i, url in enumerate(urls):
    slug = url.split('/')[-1]
    try:
        links = get_outgoing_links(url)
        if links is None:
            failed.append(url)
            print(f"[{i+1:3d}] FAILED   {slug}")
        elif len(links) == 0:
            dead_ends.append(url)
            print(f"[{i+1:3d}] DEAD-END {slug}")
        else:
            has_outlinks.append(url)
            print(f"[{i+1:3d}] LINKS({len(links):2d}) {slug}")
    except Exception as e:
        failed.append(url)
        print(f"[{i+1:3d}] ERROR    {slug}: {e}")
    time.sleep(0.15)

with open('/tmp/dead_end_urls.txt', 'w') as f:
    f.write('\n'.join(dead_ends))

print(f"\nTotal:         {len(urls)}")
print(f"Dead-ends:     {len(dead_ends)}")
print(f"Has outlinks:  {len(has_outlinks)}")
print(f"Failed:        {len(failed)} (verify manually)")
print(f"Dead-end rate: {round(len(dead_ends)/len(urls)*100)}%")

──────────────────────────────────────── ステップ 3B — 静的サイト / WORDPRESS / 従来型 CMS(標準 CURL) ────────────────────────────────────────

仕組み 静的ファイル、WordPress、Gatsby、従来型 CMS など、完全にレンダリング済みの HTML を直接配信するサイトでは、標準的な curl でリンクをすべて含むページ全体を取得できます。ナビゲーション、フッター、サイドバーのリンクを除外するため、メインコンテンツ領域のみを解析します。

import subprocess, re, time
from urllib.parse import urljoin

domain = "DOMAIN.com"
base = "https://www." + domain

with open('/tmp/blog_urls.txt') as f:
    urls = [u.strip() for u in f if u.strip()]

SKIP_EXT = (
    '.png','.webp','.jpg','.jpeg','.gif','.svg','.ico',
    '.woff2','.css','.js','.xml','.pdf','.zip'
)

def get_outgoing_links(url):
    result = subprocess.run([
        'curl', '-sL', '--max-time', '10',
        '-A', 'Mozilla/5.0 (compatible; InternalLinkBot/1.0)',
        url
    ], capture_output=True, text=True, timeout=15)

    html = result.stdout

    # Extract main content area only
    content = ''
    for tag in ['article', 'main', 'body']:
        m = re.search(rf'<{tag}[^>]*>(.*?)</{tag}>', html, re.DOTALL | re.IGNORECASE)
        if m:
            content = m.group(1)
            break
    if not content:
        content = html

    hrefs = re.findall(r'<a\\s+[^>]*href\\s*=\\s*["\']([^"\'\\ ]+)["\']', content, re.IGNORECASE)

    clean = set()
    for href in hrefs:
        href = href.strip()
        if not href or href.startswith('#') or href.startswith('mailto:') or href.startswith('tel:'):
            continue
        abs_url = urljoin(base, href)
        if domain not in abs_url:
            continue
        path = '/' + abs_url.split(domain, 1)[-1].lstrip('/')
        if any(path.lower().endswith(e) for e in SKIP_EXT):
            continue
        if abs_url.rstrip('/') == url.rstrip('/'):
            continue
        clean.add(path)
    return clean

dead_ends = []
has_outlinks = []

for i, url in enumerate(urls):
    slug = url.split('/')[-1]
    links = get_outgoing_links(url)
    if links:
        has_outlinks.append(url)
        print(f"[{i+1:3d}] LINKS({len(links):2d}) {slug}")
    else:
        dead_ends.append(url)
        print(f"[{i+1:3d}] DEAD-END {slug}")
    time.sleep(0.2)

with open('/tmp/dead_end_urls.txt', 'w') as f:
    f.write('\n'.join(dead_ends))

print(f"\nTotal:         {len(urls)}")
print(f"Dead-ends:     {len(dead_ends)}")
print(f"Has outlinks:  {len(has_outlinks)}")
print(f"Dead-end rate: {round(len(dead_ends)/len(urls)*100)}%")

──────────────────────────────────────── ステップ 3C — 不明 / 純粋な SPA — ユーザーに確認を依頼 ────────────────────────────────────────

フレームワークを特定できない場合(例:Nuxt.js、Angular、Vue SPA)、または curl でサンプルページ 3 件から <a> タグを 10 個未満しか取得できない場合は、処理を停止してユーザーに次のように伝えます。

「このサイトは、標準的な curl リクエストでは検出できないクライアントサイドレンダリングを使用しているようです。この監査を正確に実行するには、次のいずれかが必要です。

  1. このドメインの Ahrefs Site Audit プロジェクトを設定します(JS をレンダリングするヘッドレスブラウザーを使用します)。最初のクロールが完了したら、プロジェクト ID を共有してください。
  2. JavaScript レンダリングを有効にして Screaming Frog からクロール結果をエクスポートし、内部リンクの CSV を提供してください。
  3. 適切な検出ヘッダーを適用できるよう、使用している正確なフレームワークを確認してください。

少なくとも 5 件のサンプルページで検出方法が正常に機能すると確認できるまで、レポートを生成しないでください。」

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ステップ 4 — 結果を検証する ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

このステップを実施する理由 検出の不具合は気づかれないまま発生します。アセットフィルターの設定ミスや fetch の失敗により、誤った行き止まりリストがひそかに生成されることがあります。このステップでは、提案の生成前にそれを検出します。

続行する前に、次のすべてを実施してください。

  1. LIST_DEAD_ENDS からランダムに 3 ページを選びます。それぞれをブラウザーで開き、ドメイン内への発リンクが本当にないことを手動で確認します。リンクがあるページがあれば、検出スクリプトのアセットフィルターとドメインパターンを再確認してください。

  2. LIST_HAS_OUTLINKS から 3 ページを選びます。同じドメイン内の別ページへのリンクがブラウザー上で確認できることを確かめます。

  3. DEAD_END_RATE > 80% の場合は、何かが誤っている可能性があります。fetch でアセットリンクだけが返されているか、ドメインパターンが一致していない可能性があります。続行する前にデバッグしてください。

  4. DEAD_END_RATE が 0% の場合は、フィルターが緩すぎる可能性があります(アセットまたは自己リンクをカウントしている)。既知のリンクが少ないページで、どのリンクが検出されているか確認してください。

  5. ステップ 3A(Next.js)の場合、失敗リストが空でなければ、それらのページを手動で確認してください。本当に行き止まりのページもあれば、エラーが発生したページもあります。リストを確定する前に、失敗したページを個別に再実行してください。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ステップ 5 — キーワード調査 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

このステップを実施する理由 関連性の高い発リンクの提案を作成するには、各行き止まりページと、リンク先候補の各ページの内容を把握する必要があります。Ahrefs を 1 回呼び出すだけで、ブログ全体をカバーできます。

ツール:Ahrefs:site-explorer-top-pages

パラメーター:

  • target:ブログのプレフィックス(例:www.DOMAIN.com/blog/)
  • mode:prefix
  • date:本日の日付 YYYY-MM-DD
  • select:url,top_keyword,sum_traffic
  • limit:500
  • order_by:sum_traffic:desc

結果:MAP_KEYWORDS — すべてのページのキーワードとトラフィックのマップ。

MAP_KEYWORDS に含まれない行き止まりページについては、URL スラッグからキーワードを導き出します。ハイフンをスペースに置き換えてください。トラフィック数を捏造してはいけません。null を使用してください。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ステップ 6 — クラスター化して提案を生成する ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

このステップを行う理由 外部リンクの提案は、リンク切れページとトピック上の関連性がなければなりません。まずクラスター化し、次にクラスター内およびクラスター間でマッチングすることで、自然で有用な提案を作成できます。

クラスター化の方法 キーワードとタイトルを使って、リンク切れページをすべて主要なトピックごとにグループ化します。サイトのコンテンツの幅に応じて、5~15 個のクラスターを作成します。クラスター名は実際のコンテンツに合わせてください。本当に必要な場合を除き、「その他」や「分類なし」のような一般的な名前は使わないでください。

提案の生成方法 リンク切れページごとに、LIST_ALL から 3 ページを特定します(サイト内の任意のページで、他のリンク切れページに限定されません)。次の条件を満たすページを選んでください。

  1. トピックに関連していること。同じ、または隣接する分野であること
  2. リンク切れページを読んだ人にとって、自然な次のステップとなること
  3. できれば、すでに多くのリンクを受けているページであること。これにより、リンク切れページがより広範な内部リンクグラフにつながります

3 件それぞれの提案について、以下を提示してください。

アンカーテキスト リンク先のページ(リンクを設定する対象ページ)の主要キーワード。ルール:

  • 対象ページの kw フィールドを MAP_KEYWORDS から使用します
  • 対象ページにキーワードデータがない場合は、URL スラッグから導き出します
  • 文中のハイパーリンクテキストとして自然に読める必要があります
  • アンカーテキスト内およびその周辺に em ダッシュ(—)を使わないでください

配置場所 リンク切れページのどのセクションに外部リンクを挿入するかを、具体的に説明します。ルール:

  • 「記事のどこか」ではなく、セクション単位で具体的に記述します
  • 読者が自然にさらに詳しく知りたいと思う場所など、文脈に合っていること
  • 例: 「中心となる概念を定義する導入部」 「次のステップを提案する比較表の後」 「記事末尾のツールとリソースのセクション」 「記事中盤で[隣接するトピック]に初めて触れる箇所」

提案する文脈のコピー 配置場所にそのまま挿入できる文を用意します(最大 1~2 文)。ルール:

  • 対象ページの URL にリンクする、実際の HTML ハイパーリンクとしてアンカーテキストを含める必要があります: [target_kw]
  • 配置場所として説明した文脈で自然に読める必要があります
  • 「詳しくはこちら」のような文ではなく、実質的な価値を加える必要があります
  • リンク切れページ固有の文体やセクションに合わせて作成してください
  • 合計 15~35 語(HTML タグを除く)
  • コピー内で em ダッシュ(—)を使わないでください。em ダッシュはカンマに置き換えるか、文全体を組み立て直してください
  • 例: 「まず基本から始めたい場合は、開発者向けマーケティング戦略の明確な概要を読むと、これらの施策を効果的に実行するための枠組みがわかります。」 「確かなB2B コンテンツマーケティング戦略と組み合わせるチームは、オーガニックトラフィックと営業パイプラインの両方で、複利的な効果を得られます。」

各提案オブジェクトの copy フィールドとして D[] に保存します。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ステップ 7 — HTML レポートを生成する ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

コードを書く前に、必ず以下を読んでください。 → references/report-style-reference.html

参照ファイルは、すべての CSS 変数、タイポグラフィ、コンポーネントのスタイル、間隔の値、JavaScript パターンに関する唯一の基準です。新しいスタイルを独自に考案しないでください。異なる値に置き換えないでください。参照ファイルからそのまま正確にコピーしてください。

レポートヘッダーの値

Site URL:        https://www.DOMAIN.com/blog/
Total Blogs:     TOTAL_PAGES
No Outlinks:     DEAD_END_COUNT        ← red stat (fail)
Have Outlinks:   PAGES_WITH_OUTLINKS   ← green stat (pass)
Dead-End Rate:   DEAD_END_RATE%        ← amber stat (warn)
Suggestions:     DEAD_END_COUNT × 3   ← indigo stat (accent)

検出タグ(使用した方法に応じて選択):

  • Next.js: ⚑ RSC payload detection (Next.js) · YYYY-MM-DD
  • Static/WP: ⚑ Curl HTML parse · YYYY-MM-DD
  • Site Audit: ⚑ Ahrefs Site Audit (project ID: XXXX) · YYYY-MM-DD

レポートフッター: DOMAIN.com · Dead-End Pages Audit · YYYY-MM-DD / Source: [detection method]

提案カードのラベル(orphan スキルとは異なります)

  • "Link To" — 対象ページ(外部リンク先のページ)
  • "Anchor Text" — 対象ページの主要キーワード
  • "Where to Place" — リンク切れページ内のセクション
  • "Context Copy" — リンクを埋め込んだ、すぐに貼り付けられる文

データ構造 — D[] 配列

リンク切れページごとに 1 つのオブジェクト:

{
  url:     "https://www.DOMAIN.com/blog/page-slug",  // dead-end page URL
  title:   "Page Title",                              // display title
  kw:      "top keyword",                             // dead-end page's own keyword
  tr:      123,                                       // monthly traffic or null
  cluster: "Cluster Name",                            // must match a key in CC{}
  s: [
    {
      from:    "Target Page Title",                   // page to link OUT TO
      fromUrl: "https://www.DOMAIN.com/blog/target",  // target URL
      anchor:  "target page keyword",                 // target's top keyword
      place:   "Where in the dead-end page to add the link",
      copy:    "Sentence with <a href=\"...\">target keyword</a> embedded."
    },
    // × 3 per dead-end page
  ]
}

フィールドの意味に関する注意 from と fromUrl はソースページではなく、リンク先のページ(リンクされるページ)を指します。孤立スキルとは逆の方向です。JS カードビルダーでは、これらを「ソースページ」ではなく「リンク先」として表示してください:

// In the suggestion card builder:
`<div class="sug-from-label">Link To</div>
 <div class="sug-from-title">${s.from}</div>
 <a class="sug-from-url" href="${s.fromUrl}" target="_blank">...</a>`

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 厳守事項 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  1. HTML または CSS を書く前に、references/report-style-reference.html を必ず読んでください。 必須です。例外はありません。

  2. リンク検出の前に、必ずステップ 2(フレームワークの検出)を実行してください。ドメイン名やサイトの見た目を根拠に、フレームワークを決めつけないでください。

  3. Next.js のサイトでは、必ず RSC ヘッダー方式(ステップ 3A)を使用してください。Next.js のサイトで標準の curl を使わないでください。空の JS シェルが返され、誤った結果になります。

  4. ページごとに、RSC の取得が成功したことを必ず検証してください。応答が 10KB 未満、または React マーカーが見つからない場合は、取得に失敗しています。該当ページにフラグを付け、行き止まりとして黙って記録しないでください。

  5. アセットへのリンク(画像、フォント、スクリプト、スタイルシート)を、有効な発リンクとして数えないでください。/logo.png と /styles.css のみにリンクしているページは行き止まりです。

  6. 自己リンク(ページ自身へのリンク)を、有効な発リンクとして数えないでください。

  7. 提案を生成する前に、必ずサンプルページで結果を検証してください(ステップ 4)。行き止まり率が 80% を超える場合、または 0% の場合は、何かが間違っています。まずデバッグしてください。

  8. アンカーテキストには、行き止まりページではなく、リンク先ページの上位キーワードを使用してください。

  9. 提案する文脈の文面では、em ダッシュ(—)を絶対に使用しないでください。カンマに置き換えるか、文の構成を変えてください。

  10. 行き止まりページごとに、必ず提案を 3 件作成してください。それより多くても少なくてもいけません。

  11. 行き止まりページの URL と、リンク先ページの URL 3 件すべてを、target="_blank" で開くクリック可能な <a href> リンクにしてください。

  12. report-style-reference.html の CSS 値を絶対に変更しないでください。

  13. 必ず D[] と CC[] を使用して、レポートをデータ駆動で構築してください。個々のブログカードの HTML を静的に記述しないでください。

  14. 提案を生成する前に、必ず LIST_DEAD_ENDS を整理してください。200 以外のページ、ページネーション、タグ/カテゴリーページ、および単独の記事ではない URL を削除してください。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 例外的なケース ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

NEXT.JS の APP ROUTER と PAGES ROUTER RSC ヘッダーは App Router(Next.js 13 以降)で動作します。古い Pages Router のサイトでは、RSC ペイロードを取得できない場合があります。確認方法:複数のページで一貫して応答が 10KB 未満なら、Pages Router を使用している可能性があります。標準の HTML 応答に NEXT_DATA が含まれているか確認してください。シリアライズされたページのプロパティが含まれており、リンクデータが含まれている場合があります。

CDN がヘッダーを削除するサイト CDN の設定によっては、カスタムリクエストヘッダーがオリジンに届く前に削除されることがあります。RSC の応答が一貫して 10KB 未満の場合は、URL の末尾にキャッシュ回避用クエリパラメーター ?_rsc=1 を追加してテストしてください。それでも失敗する場合、CDN が RSC ヘッダーを削除している可能性が高いため、NEXT_DATA を使う方法に切り替えてください。

大規模サイト(200 ページ以上) ブログページが 200 ページを超えるサイトでは、レート制限を避けるため、検出を 50 件ずつのバッチに分け、リクエスト間に 0.3 秒の遅延を設けて実行してください。まず MAP_KEYWORDS のトラフィック上位ページを優先してください。時間に制約がある場合は、トラフィック上位 100 ページを監査し、監査対象が上位 100 ページであることをレポートに記載してください。

すべてのページが行き止まり(率 = 100%) これは、検出に完全に失敗したことを意味します。ドメインのパターンが間違っているか、アセットのフィルターが厳しすぎます。デバッグ確認を実行し、既知のリンクを持つページを 3 件選んで、見つかった生のリンクを出力してください。何も表示されない場合は、正規表現を修正してください。アセットしか表示されない場合は、SKIP_EXT/SKIP_PREFIX ルールの範囲を広げてください。

行き止まり率が 0% すべてのページに発リンクがあるように見えます。フィルターが緩すぎる可能性があり、ナビゲーションやフッターのリンクを数えていることが考えられます。リンク検索を <article> または <main> タグのコンテンツ領域のみに制限し、is_content フィルタリングを追加してください。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 最終指標の計算 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ TOTAL_PAGES = len(LIST_ALL) DEAD_END_COUNT = len(LIST_DEAD_ENDS) PAGES_WITH_OUTLINKS = TOTAL_PAGES - DEAD_END_COUNT DEAD_END_RATE = round(DEAD_END_COUNT / TOTAL_PAGES * 100) TOTAL_SUGGESTIONS = DEAD_END_COUNT × 3

コメント