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?

個人開発サイトの駅別LPを30駅→1,400駅に拡大した全記録

0
Posted at

はじめに

個人開発でバイクポータルサイト MotoHub を運営しています。

前回の記事で「駅別バイク駐車場ランディングページ」の実装について書きました。

📝 前回記事: [国交省の駅データで全国9,000駅のSEO LPを作る時の3つの落とし穴]image.png
image.png

この記事では、その駅別LPを30駅→1,400駅に拡大した全記録を書きます。

  • なぜ拡大したのか
  • どのようにsitemapを設計したか
  • 一覧ページのUIをどう工夫したか
  • 実装上の判断ポイント

「個人開発でSEOのカバレッジを増やしたい」「UXを崩さず内部リンクを増やしたい」という方の参考になれば幸いです。


拡大前の状態

サイトの構成

/parking/station/              一覧ページ(主要30駅のみ表示)
/parking/station/tokyo         東京駅の詳細ページ
/parking/station/shinjuku      新宿駅の詳細ページ
...

全国9,032駅のマスタデータはDBにあり、個別URLは全駅でアクセス可能。しかし:

  • 一覧ページには主要30駅のみ表示
  • sitemapにも主要30駅のみ登録(31 URL)

つまり、9,032駅のマスタデータがあるのに、Google には31URL しか認識させていない状態でした。

なぜ最初は30駅に絞ったのか

実装初期は、以下の理由で意図的に主要駅のみに絞りました。

  1. 低品質ページ量産によるペナルティ回避
    • 駐車場が0件 or 1件の駅を大量公開すると「低品質コンテンツ」と判定されるリスク
  2. クロールバジェットの有効活用
    • 主要URLにクロールを集中させたい
  3. 効果検証の段階的実施
    • まず30駅で仕組みが機能するか確認

しかし30駅だと、カバレッジが狭すぎるという問題が見えてきました。

課題:9,000駅あるのに31URLだけ?

ユーザー視点の違和感

  • 「自分の最寄り駅の情報が見られない」
  • 「地方の駅はサポート対象外?」
  • 「サイト内回遊で他の駅を発見できない」

SEO視点の機会損失

  • 「○○駅 バイク 駐車場」のロングテールクエリを取りこぼしている
  • 地方駅からの流入ゼロ
  • 内部リンクが少なく、個別ページのページランクが上がりにくい

本番データで計算してみた

駐車場件数ごとに、sitemapに登録できる駅数を集計:

docker compose exec app php artisan tinker --execute="
use App\Models\Station;
foreach ([1, 3, 5, 10, 20] as \$min) {
    echo sprintf('%d件以上: %d駅', \$min, Station::has('bikeParkings', '>=', \$min)->count()) . PHP_EOL;
}
"

結果(本番データ):

駐車場件数 駅数
1件以上 3,783駅
3件以上 2,049駅
5件以上 1,370駅 ← 採用
10件以上 624駅
20件以上 220駅

5件以上の駅 = 駐車場を比較検討できる「意味のあるページ」の閾値と判断し、これを採用。

31 URL → 約1,400 URL(主要30駅 + 非主要1,370駅 + 一覧ページ)、44倍のカバレッジ拡大を目指しました。

設計判断①:sitemapに何を入れるか

考慮したポイント

sitemap登録の判断は、以下の要素のバランスで決まります。

要素 重視すべき時
カバレッジ 検索流入を最大化したい
品質 ペナルティリスクを避けたい
クロール効率 クロールバジェットを有効活用したい

「5件以上」を選んだ理由

カバレッジと品質のバランスが最適だから。

  • 5件以上 = ユーザーが駐車場を比較検討できる最低ライン
  • 3件だと「選択肢が少ない」と感じさせる
  • 10件だとカバレッジが狭すぎる

Phase分けで段階的に拡大

いきなり全駅を公開せず、段階的拡大の設計に。

Phase 1(今回):5件以上(1,370駅)
Phase 2(1ヶ月後):3件以上(2,049駅)
Phase 3(2ヶ月後):1件以上(3,783駅)

各Phaseで GSCのデータを見て、問題なければ次へ。

設計判断②:一覧ページのUIをどうするか

sitemap登録は簡単ですが、一覧ページに1,400駅を表示する方法は一工夫必要です。

候補1: 全駅リスト表示

シンプルに全駅を縦スクロールで表示。

問題点:

  • 1,400駅の縦スクロール地獄
  • スマホで使い物にならない
  • 主要駅がその他駅に埋もれる

候補2: ページネーション

ページ分割して表示。

問題点:

  • 内部リンクの効率が悪い(1ページ1クリック先にある)
  • ユーザーが1ページ目で離脱
  • Google の内部リンク評価も分散

候補3: 二階層 + アコーディオン(採用)

主要30駅は常時表示、その他駅は都道府県別にアコーディオン

メリット:

  • 主要駅の目立たせが可能
  • その他駅も発見可能(クリックで展開)
  • スマホでも使いやすい
  • 全駅への内部リンクが1ページに存在

デメリット:

  • JavaScript必須
  • 初期表示の駅数は抑制

採用したUI構造

【主要駅セクション】
[東京] [新宿] [渋谷] [池袋] [上野] ...(30駅、常時表示)

【その他の駅セクション】
駐車場5件以上の駅が1,370駅あります。都道府県別に見る:

▼ 東京都(450駅)  ← クリックで展開
  [初台] [新宿御苑前] [四谷三丁目] ...

▼ 神奈川県(120駅)  ← デフォルト折りたたみ
  ...

▼ 埼玉県(90駅)
  ...

image.png

実装

ステップ1: StationParkingService の拡張

既存のサービスクラスに、駅一覧取得メソッドを追加。

// app/Services/Parking/StationParkingService.php

public function getAllStationsForIndex(): array
{
    return Cache::remember('stations_index', 86400, function () {
        $allStations = $this->getSitemapStations(5); // 既存メソッドを再利用
        
        return [
            'major' => $allStations->where('is_major', true)
                ->groupBy('prefecture'),
            'others' => $allStations->where('is_major', false)
                ->groupBy('prefecture'),
        ];
    });
}

ポイント: 既に実装済みの getSitemapStations(5) を再利用。DRY原則を守りつつ、sitemapと一覧ページでデータの整合性を担保。

ステップ2: Controller の修正

// app/Http/Controllers/Parking/StationParkingController.php

public function index(StationParkingService $service)
{
    $stations = $service->getAllStationsForIndex();
    
    return view('parking.station-index', compact('stations'));
}

ステップ3: Blade テンプレート

{{-- resources/views/parking/station-index.blade.php --}}

<section class="major-stations">
    <h2>主要30駅(すぐ見れる)</h2>
    @foreach($stations['major'] as $prefecture => $list)
        <div class="prefecture-group">
            <h3>{{ $prefecture }}</h3>
            @foreach($list as $station)
                <a href="{{ route('parking.station.show', $station->slug) }}" 
                   class="station-chip">
                    {{ $station->name }}駅
                </a>
            @endforeach
        </div>
    @endforeach
</section>

<section class="other-stations">
    <h2>その他の駅(駐車場5件以上)</h2>
    <p>下記の駅にも駐車場情報が充実しています。</p>
    
    @foreach($stations['others'] as $prefecture => $list)
        <details class="prefecture-accordion">
            <summary>
                ▼ {{ $prefecture }}({{ $list->count() }}駅)
            </summary>
            <div class="stations-grid">
                @foreach($list as $station)
                    <a href="{{ route('parking.station.show', $station->slug) }}">
                        {{ $station->name }}駅
                    </a>
                @endforeach
            </div>
        </details>
    @endforeach
</section>

ポイント: HTML5 の <details> / <summary> タグを使えば、JavaScriptなしで折りたたみUI が実現できる。余計なライブラリを入れなくて済む。

ステップ4: sitemap の拡張

GenerateSitemap.php を修正し、5件以上の駅を sitemap に含める。

// app/Console/Commands/GenerateSitemap.php

private function getStationsForSitemap()
{
    return app(StationParkingService::class)->getSitemapStations(5);
}

パフォーマンス対策

課題:1,400駅×空間インデックス計算は重い

駅と駐車場の紐付けを毎回計算すると、本番で 0.5〜1秒かかる。アクセスが集中するとDB負荷が跳ね上がる。

対策:24時間キャッシュ

Cache::remember('stations_index', 86400, function () {
    // 重い処理
});
  • 初回アクセス:0.53秒
  • 2回目以降:キャッシュから0.01秒

駅と駐車場の紐付けは頻繁に変わらないので、24時間キャッシュで十分。

補助:グリッドベース空間インデックス

1,400駅 × 40,000駐車場の総当たりは現実的じゃないので、緯度経度をグリッドに分割して検索範囲を絞る方式を採用。

// 対象駅の500m圏の駐車場だけ取得
$parkings = BikeParking::inBoundingBox(
    $station->latitude,
    $station->longitude,
    0.5 // km
)->get();

これで計算量が O(N*M) → O(N+k) に削減。

SEO効果の測定(予測)

期待される変化

指標 Before 予測 After
sitemap URL数 31 約1,400
内部リンク数 約30 約1,400
インデックス登録URL ~20 ~1,000
「○○駅 バイク 駐車場」系流入 主要都市のみ 全国対応

想定する流入増

1〜2ヶ月後に、GSC データを見ることで実測値を取得予定です。

image.png
image.png

個人開発としての学び

① 「量」と「質」のバランス設計

最初から全駅を公開するのは簡単。でも品質を担保する閾値を設けることで、Google に信頼されるサイトを維持できます。

「駐車場5件以上」というゲートは、ユーザーにもGoogleにも「意味のあるページだけ」という信頼を与えます。

② 既存コードを再利用

sitemapと一覧ページで、同じデータソース(getSitemapStations(5)) を使用。データの整合性を保ちつつ、メンテナンスコストを削減。

③ HTML5 ネイティブ機能の活用

<details> / <summary> でアコーディオンUIを実現。JavaScriptのライブラリを増やさない判断。

④ Phase 分けで段階的拡大

一度に全部公開するのではなく、Phase 1 → GSC観察 → Phase 2 とステップを踏むことで、リスクを最小化。

まとめ

個人開発でも、設計の工夫次第でSEOは劇的に改善できます。

今回の実装では:

  1. sitemapの品質ゲート(駐車場5件以上)
  2. UI の情報階層設計(主要駅+アコーディオン)
  3. 既存コードの再利用(DRY原則)
  4. パフォーマンス対策(24時間キャッシュ)
  5. 段階的Phase展開(Phase 1→2→3)

この5つのポイントで、31URL → 1,400URL の拡大を実現しました。

個人開発者でも、大規模なSEO施策は可能です。データと設計があれば。

参考リンク

出典

本記事で紹介したサービスの駅情報は、国土交通省「国土数値情報(鉄道駅データ)」(https://nlftp.mlit.go.jp/)を加工して利用しています。

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?