Jekyll多言語ブログのURL構造変更とページネーション全面刷新の記録

URL構造整理ダイアグラム

URLが気になっていました

このブログは韓国語、英語、日本語の3言語に対応しています。ですが、URL構造に少し問題がありました。

韓国語: /2026/02/21/claude-code-1m-cost-reality/
英語:   /2026/02/21/claude-code-1m-cost-reality-en/
日本語: /2026/02/21/claude-code-1m-cost-reality-ja/

英語記事のURLの末尾に-enが付いているのが、どうにも気になっていました。見た目もよくないですし、Googleも言語別コンテンツは/en/のようなサブディレクトリ構造を推奨しています。記事が増えれば増えるほど、後から変更するのは大変になります。

目指したのはこの構造です。

graph LR
    subgraph 変更前
        A["/日付/slug/"] --- B["韓国語"]
        C["/日付/slug-en/"] --- D["英語"]
        E["/日付/slug-ja/"] --- F["日本語"]
    end
    subgraph 変更後
        G["/日付/slug/"] --- H["韓国語"]
        I["/en/日付/slug/"] --- J["英語"]
        K["/ja/日付/slug/"] --- L["日本語"]
    end

Jekyllでは記事ごとにpermalinkを指定できます。英語・日本語の記事のfront matterに以下を追加しました。

# 英語記事のfront matter
permalink: /en/:year/:month/:day/:title/
redirect_from:
  - /2026/02/21/claude-code-1m-cost-reality-en/

permalinkで新しいURLを指定し、redirect_fromで旧URLから自動リダイレクトされるようにしました。jekyll-redirect-fromプラグインが必要ですが、GitHub Pagesで公式サポートされているため、_config.ymlに1行追加するだけです。

# _config.yml
plugins:
  - jekyll-redirect-from

次に、_layouts/default.htmlのhreflangタグも修正しました。

<!-- 変更前 -->
<link rel="alternate" hreflang="en" href="https://realcoding.blog-en/" />

<!-- 変更後 -->
<link rel="alternate" hreflang="en" href="https://realcoding.blog/en-en/" />

ただ、本当に大変だったのはその先でした。本文中にハードコードされたリンクが152箇所あり、すべて修正する必要がありました。

<!-- 変更前 -->
[Escaping Compacting Hell](/2026/02/20/claude-code-1m-context-review-en/)

<!-- 変更後 -->
[Escaping Compacting Hell](/en/2026/02/20/claude-code-1m-context-review-en/)

結果:140ファイル変更、569行追加、154行削除。かなり大がかりな作業でした。

ところがページネーションが壊れました

URL構造を変更したら、ページネーションに問題が発生しました。Jekyllのjekyll-paginateプラグインはルートディレクトリ(/)でしか動作しないという既知の制約があります。

韓国語のトップページ(/)では問題なく動いていましたが、/en//ja/ではpaginatorオブジェクト自体が存在しません。サーバーサイドのLiquidテンプレートでは対処できませんでした。

JavaScriptで全面刷新

そこで、クライアントサイドのJavaScriptページネーションに切り替えました。仕組みはシンプルです。

  1. Liquidですべての記事をHTMLにレンダリング
  2. JSが10件ずつdisplay: none/blockで表示を切り替え
  3. ?page=2のURLパラメータでページを管理

コアとなるコードはこちらです。

(function() {
  var POSTS_PER_PAGE = 10;
  var posts = document.querySelectorAll('.posts-list .post-item');
  if (posts.length <= POSTS_PER_PAGE) return;

  var totalPages = Math.ceil(posts.length / POSTS_PER_PAGE);
  var params = new URLSearchParams(window.location.search);
  var currentPage = parseInt(params.get('page')) || 1;

  function showPage(page) {
    var start = (page - 1) * POSTS_PER_PAGE;
    var end = start + POSTS_PER_PAGE;
    for (var i = 0; i < posts.length; i++) {
      posts[i].style.display = (i >= start && i < end) ? '' : 'none';
    }
  }
  showPage(currentPage);
})();

このスクリプトをindex.htmlen/index.htmlja/index.htmlの3箇所に配置しました。多言語ラベルは_data/*.ymlに追加しています。

# _data/ja.yml
posts:
  prev_page: "前へ"
  next_page: "次へ"

以前は韓国語のみ/page2//page3/形式でページネーションが機能していましたが、今は3言語すべてが?page=2方式に統一されました。

まとめ

URL構造は記事が蓄積される前に、早めに設計しておくべきです。後から変更すると、140以上のファイルを修正する作業が待っています。多言語Jekyllブログであれば、ルート以外のページには最初からJavaScript方式のページネーションを検討するのが現実的です。