Article

サイト構造の見直し術:情報アーキテクチャ最適化でユーザー導線を改善

高田晃太郎
サイト構造の見直し術:情報アーキテクチャ最適化でユーザー導線を改善

ユーザーは到達後の最初の約10秒が離脱か継続かの判断に極めて重要というNielsen Norman Groupの知見は、プロダクトに関わる誰もが一度は目にしたことがあるはずです¹。サイト内の探索行動に目を向けると、一般にクリック深度が3層を超えたページは直帰率が相対的に高くなる傾向が報告されています。情報アーキテクチャ(IA:コンテンツの構造化とラベル設計)が曖昧なまま規模だけが増したサイトでは、ユーザー導線のロスと同時に検索エンジンのクロール効率(検索エンジンが巡回に割けるリソースの使われ方)も低下します²。技術とビジネスの橋渡しを担うCTOやエンジニアリングマネージャーにとって、IAの見直しは単なるナビゲーションの整理ではなく、計測・実装・移行を伴う全社プロジェクトです。ここでは指標、診断、設計、そして移行の要点を、再現可能なコードと共に実務の観点で紐解きます。

IA見直しの核心:どの指標がビジネスを動かすか

情報アーキテクチャの再設計で最初に定義すべきは、成功を測る物差しです。ユーザー側の体験ではタスク成功率(目的達成の割合)、探索時間(目標までにかかる時間)、クリック深度(ホームからのステップ数)、回遊率(サイト内での移動の活発さ)、検索からの着地とサイト内発見(ナビゲーション/サイト内検索)のバランスが主要な観点になります⁷。ビジネス側ではCVRや有効リード比率に加え、コンテンツの貢献度をセッション単位ではなくジャーニー単位で評価することが重要です。検索エンジン側の視点も無視できません。クロールの無駄打ちを抑え、重要URLの発見と再訪周期を最適化することは、特に規模の大きいサイトでインデックスの鮮度と露出の安定性に直結します²。こうした多面的な指標を一枚のダッシュボードに収めることで、意思決定のスピードが上がり、施策の反復も早まります。一般に、サイト構造とタクソノミーの見直しは探索時間やCTAのクリック率の改善につながることが多い一方、改善幅はサイトの規模、ドメイン、既存の課題によって大きく変動します。効果測定の設計と検証計画を最初に用意し、過度な期待値を避けながら段階投入で最適化を重ねるのが堅実です。

クリック深度は「目安」であり、単独最適化の罠を避ける

よく知られる三クリックルールは神話ですが、だからといって深度を気にしなくてよいわけではありません。深度が浅くても情報の匂い(リンク文言や周辺コンテキストが示す見込みの情報量)が弱ければ迷子になり、逆にやや深くても明確に導けるならタスクは完了します⁶。深度は匂いの強度、文脈の一貫性、見出し語の一致度とセットで評価しましょう。測定時はセッション内での目標発見までのステップ数、検索クエリと着地タイトルの語彙一致率、パンくずの利用有無といった行動ログを組み合わせると、改善余地が立体的に浮かび上がります。

クロール効率は構造で決まる:重要URLへの内部リンク密度

クロールバジェットを左右するのは速度だけではありません。重要URLに対する内部リンク密度(他ページからの参照数)、リンクの再現性、サイトマップと実体の一致度が効いてきます²。ハブページとガイド記事を設け、関連性のあるノードを束ねることで、グラフ全体の到達性が改善し、インデックスの安定にも寄与します²。構造が整うと、検索結果のパンくず表示やサイトリンク表示の機会も増え、外部からの入口が増幅されます³。

現状診断を自動化する:ログ、グラフ、行動データ

IAの見直しは現状把握から始まります。解析タグの画面上イベントだけでなく、サーバーログ、クローラー、BigQueryの生データを横断して、構造上のボトルネックを特定します。ここでは、クリック深度の推定、孤立ページの検出、セッション内の探索経路の再構成を、動かせるコードで示します。

# Code 1: クリック深度を推定する簡易クローラ(BFS)
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin, urlparse
from collections import deque
import urllib.robotparser as robotparser
import csv

ROOT = "https://example.com/"
MAX_PAGES = 5000
TIMEOUT = 8

session = requests.Session()
session.headers.update({"User-Agent": "IA-AuditBot/1.0 (+https://example.com)"})

rp = robotparser.RobotFileParser()
rp.set_url(urljoin(ROOT, "/robots.txt"))
try:
    rp.read()
except Exception:
    pass  # robotsが取れない場合は後段でallowを都度確認

def same_host(url):
    return urlparse(url).netloc == urlparse(ROOT).netloc

visited = set()
depth = {ROOT: 0}
q = deque([ROOT])

while q and len(visited) < MAX_PAGES:
    url = q.popleft()
    if url in visited:
        continue
    if not rp.can_fetch(session.headers["User-Agent"], url):
        continue
    try:
        resp = session.get(url, timeout=TIMEOUT)
        if resp.status_code >= 400:
            continue
        visited.add(url)
        soup = BeautifulSoup(resp.text, "html.parser")
        for a in soup.find_all("a"):
            href = a.get("href")
            if not href:
                continue
            nxt = urljoin(url, href)
            if not same_host(nxt):
                continue
            if "#" in nxt:
                nxt = nxt.split("#")[0]
            if nxt not in depth:
                depth[nxt] = depth[url] + 1
                q.append(nxt)
    except requests.RequestException:
        continue

with open("depth.csv", "w", newline="", encoding="utf-8") as f:
    w = csv.writer(f)
    w.writerow(["url", "depth"])
    for u, d in depth.items():
        w.writerow([u, d])
print(f"pages={len(visited)} max_depth={max(depth.values())}")

この簡易クローラで深度分布を把握し、ハブ不足や深すぎる枝を見つけます。実行前にROOTを自サイトのドメインに置き換え、robots.txtの許可範囲内で実行してください。MAX_PAGESやTIMEOUTは本番トラフィックへの影響を避けるために小さく始め、段階的に拡張します。探索経路の行動側は、GA4のエクスポートデータ(BigQuery)からセッション内の遷移列を復元するのが堅実です。以下はBigQueryでページシーケンスと深度を推定する例です。

-- Code 2: GA4生データからセッション内ページ遷移と深度を推定
WITH base AS (
  SELECT
    event_timestamp,
    user_pseudo_id,
    (SELECT value.int_value FROM UNNEST(event_params) WHERE key = 'ga_session_id') AS session_id,
    (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'page_location') AS page_location,
    (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'page_referrer') AS page_referrer
  FROM `project.dataset.events_*`
  WHERE event_name = 'page_view'
), seq AS (
  SELECT
    user_pseudo_id,
    session_id,
    page_location,
    page_referrer,
    event_timestamp,
    ROW_NUMBER() OVER (PARTITION BY user_pseudo_id, session_id ORDER BY event_timestamp) AS step
  FROM base
)
SELECT
  user_pseudo_id,
  session_id,
  COUNTIF(step <= 3) OVER (PARTITION BY user_pseudo_id, session_id) AS within3_firststeps,
  APPROX_QUANTILES(step, 100)[OFFSET(50)] OVER () AS approx_median_depth_overall
FROM seq
LIMIT 1000;

孤立ページの検出は内部リンクグラフの入次数を見ると効率的です。CMSや静的生成の段階でリンク関係を出力できるなら、単純な集計で候補を洗い出せます。

-- Code 3: 内部リンクの入次数で孤立ページ候補を抽出
WITH links AS (
  SELECT source_url, target_url FROM `project.dataset.internal_links`
), indeg AS (
  SELECT target_url, COUNT(*) AS in_degree FROM links GROUP BY target_url
)
SELECT target_url, in_degree
FROM indeg
WHERE in_degree = 0
ORDER BY target_url;

数値は常に相関で解釈し、UXレビューや実地観察と照合します。深度が深くても、シリーズ記事のように文脈の繋がりが強ければタスク成功率は高止まりすることもあります。計測は意思決定の材料であり、設計の代替ではありません。

設計原則:ドメイン、タクソノミー、URL戦略を一気通貫で

現状が見えたら、ドメインモデリングから始めます。エンティティと関係を言語化し、カテゴリ、トピック、パターン、ユースケースの階層を定義します。技術組織にとって重要なのは、モデルがCMSやGraphQLスキーマとしてそのまま実装でき、運用の回転に耐えることです。以下はカテゴリとトピックを明示したGraphQL SDLの例です。

# Code 4: IAを反映したGraphQL SDLの一例
type Category { id: ID! slug: String! name: String! parent: Category }

type Topic { id: ID! slug: String! name: String! category: Category! }

type Article {
  id: ID!
  slug: String!
  title: String!
  category: Category!
  topics: [Topic!]!
  canonicalUrl: String!
  updatedAt: String!
}

type Query {
  category(slug: String!): Category
  topic(slug: String!): Topic
  articlesByCategory(slug: String!, limit: Int = 20, offset: Int = 0): [Article!]!
}

URLは人間の可読性と変更の安定性を両立させます。スラッグは日本語でも構いませんが、長期運用ではローマ字や英語の方が互換性が高く、翻訳や多言語展開にも有利です。パンくずとスキーマを揃えると、検索結果での表示整形にも効果が及びます³。

// Code 5: JSON-LDのパンくず(BreadcrumbList)
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {"@type": "ListItem", "position": 1, "name": "ナレッジ", "item": "https://example.com/knowledge"},
    {"@type": "ListItem", "position": 2, "name": "情報アーキテクチャ", "item": "https://example.com/knowledge/ia"},
    {"@type": "ListItem", "position": 3, "name": "サイト構造の見直し", "item": "https://example.com/knowledge/ia/site-structure"}
  ]
}

設計の段階では、ハブページを中心に情報の匂いを強化し、関連コンテンツの束ね方を決めます。タグ乱立による希釈を避け、カテゴリーとトピックの二軸で整理すると、ユーザーの意図と検索エンジンの理解が揃います⁵。重複コンテンツやシリーズ記事は、正規化URLと内部リンクの役割を明確にし、迷路を回遊路へと変えます。命名は短く、曖昧語を避け、並列語彙の一貫性を守ることが、日々の運用コストを確実に下げます。

実装と移行:リダイレクト、サイトマップ、計測の再定義

構造の変更はURL移行とセットになります。壊れたリンクは信頼を損ない、クロールの無駄打ちを増やします。301の網羅とキャッシュ制御を適切に設計し、構成の複雑化を避けます。以下はNginxで正規化とマップ型リダイレクトを併用する例です⁴。

# Code 6: Nginxでの正規化とマップ型リダイレクト
map $request_uri $redirect_target {
    default "";
    ~/old-ia/(.*)$ /knowledge/ia/$1;  # パターン移行
    =/old-guides/site-structure /knowledge/ia/site-structure; # 単発
}

server {
    listen 443 ssl;
    server_name example.com;

    if ($request_uri ~* "//") { return 301 $scheme://$host$uri; }
    if ($request_method !~ ^(GET|HEAD)$) { return 405; }

    if ($redirect_target != "") {
        return 301 $redirect_target;
    }

    location / {
        try_files $uri $uri/ /index.html;
    }
}

サイトマップは構造の宣言です。静的に用意できるならそれが最も安定しますが、規模が大きく更新頻度も高い場合はアプリケーション側での生成が現実的です。大規模サイトではサイトマップ整備がクロールの効率化に寄与します²。Expressとsitemapライブラリでの実装例を示します。

// Code 7: Node.js/Expressでのサイトマップ生成
import express from 'express'
import { SitemapStream, streamToPromise } from 'sitemap'
import { createGzip } from 'zlib'

const app = express()

async function fetchUrls() {
  // DBやCMSから取得する想定
  return [
    { url: '/knowledge', changefreq: 'weekly', priority: 0.6 },
    { url: '/knowledge/ia', changefreq: 'weekly', priority: 0.7 },
    { url: '/knowledge/ia/site-structure', changefreq: 'weekly', priority: 0.8 }
  ]
}

app.get('/sitemap.xml', async (req, res) => {
  try {
    res.header('Content-Type', 'application/xml')
    res.header('Content-Encoding', 'gzip')
    const smStream = new SitemapStream({ hostname: 'https://example.com' })
    const pipeline = smStream.pipe(createGzip())
    const urls = await fetchUrls()
    for (const u of urls) smStream.write(u)
    smStream.end()
    const data = await streamToPromise(pipeline)
    res.send(data)
  } catch (e) {
    res.status(500).end()
  }
})

app.listen(3000)

移行直後は、301のヒット率、404の発生源、クロールの滞留箇所を重点的に監視します⁴。深度分布とタスク成功率を比較するため、移行前後のデータを同一の定義で再集計し、効果の過大評価や過小評価を避けます。実務では、旧URLのロングテールが長く続くため、アクセスがゼロになったらリダイレクト設定を外すという短絡は推奨しません。リダイレクトは構造の延長線であり、資産の継承手段です。

# Code 8: 深度メトリクスの前後比較(pandas)
import pandas as pd

before = pd.read_csv('before_depth.csv')  # url, depth
after = pd.read_csv('after_depth.csv')

m_before = pd.Series(before['depth']).median()
m_after = pd.Series(after['depth']).median()

print({
  'median_depth_before': float(m_before),
  'median_depth_after': float(m_after),
  'improvement_pct': round((m_before - m_after) / m_before * 100, 1)
})

実装の仕上げに、計測と構造の対応関係をドキュメント化します。パンくず、カテゴリ、トピック、URL、コンポーネントの責務、そしてGA4のディメンション定義を一つのスキーマとして保守すると、チームの合意形成や将来の拡張が容易になります。

ケーススタディの示唆:段階投入と検証スプリント

あるエンタープライズSaaSの知識ベースを想定した設計演習では、テーマ別に散在していた大量の記事をカテゴリ×トピックで再編し、ハブページを導入することで、ユーザーの探索行動が短縮し、検索経由の直帰率やコンタクトフォーム到達率の改善が見込めます。移行は領域ごとにスプリントで小さく投入し、各スプリントで深度、探索時間、CTA CTRを検証して次スプリントに反映する形が有効です。成果の大半は情報の匂いの整備とハブ設計に起因することが多く、単に階層を浅くするだけでは不十分です。鍵は語彙の統一とクロスリンクの一貫性、そして検証サイクルの短さにあります。

まとめ:構造はプロダクト戦略であり、継続的な運用が成果を固定化する

情報アーキテクチャの最適化は、一度きりの大掃除ではありません。ビジネスが変われば、ユーザーの探索意図も言語も変わります。今日の正解を明日の負債にしないために、測定と設計と実装を同じテーブルに置き、短いスプリントで検証を回し続ける体制が求められます。まずは現状のクリック深度と孤立ページを可視化し、次に語彙とタクソノミーを決め、ハブとパンくずで匂いを強化しながらURL移行を設計していく。この一連の流れが整うと、ユーザー導線の摩擦は減り、クロールの効率が上がり、チームの作業も軽くなります。あなたのプロダクトで最初に整えるべき匂いはどこにありますか。今日のスプリントに一つだけ盛り込むとしたら、どのハブを立てますか。構造は体験の骨組みであり、骨組みが整えば、価値は自然と見つけられるようになります。

参考文献

  1. Jakob Nielsen. Page-Abandonment Time. U-Site (Nielsen Norman Group日本語記事). https://u-site.jp/alertbox/page-abandonment-time
  2. Google Search Central. Managing crawl budget for large sites. https://developers.google.com/search/docs/crawling-indexing/large-site-managing-crawl-budget
  3. Google Search Central. Breadcrumb structured data. https://developers.google.com/search/docs/appearance/structured-data/breadcrumb
  4. Google Search Central. 301 redirects. https://developers.google.com/search/docs/crawling-indexing/301-redirects
  5. Louis Rosenfeld. Putting SEO in Its Place: An Information Architecture Strategy. UXmatters. https://www.uxmatters.com/mt/archives/2011/12/putting-seo-in-its-place-an-information-architecture-strategy.php
  6. Nielsen Norman Group. Information Scent: How Users Decide Where to Go Next. https://www.nngroup.com/articles/information-scent/
  7. AddSearch. Site Search vs Navigation: How Your Users Interact with Your Website. https://www.addsearch.com/blog/site-search-vs-navigation/