2026.06.11 (木)

✨ GPT-5.5の要約  

検索結果だけを JavaScript の文字列で別に描画していたため、日付、カテゴリ、閲覧数が抜けていた問題を、共通 Liquid カードと検索オーバーレイのレイアウト整理で合わせた記録。

検索結果が古く見えた。

ホームやカテゴリ一覧では、記事カードの形はある程度整っていた。タイトルの下に日付があり、カテゴリバッジも付き、閲覧数も同じメタ行に表示されていた。

しかし検索を開くと、急に古い UI が出てきた。

タイトルと抜粋だけが表示され、いつ書いた記事なのかすぐに分からなかった。カテゴリもない。閲覧数もない。同じ記事なのに、通常の一覧では文脈があり、検索結果では抜けていた。

原因は、検索結果だけを別の JavaScript 文字列で描画していた構造だった。

JavaScript の文字列レンダリングが検索結果を古く見せていた

検索そのものは Lunr が担当している。

静的な Jekyll ブログでは、ビルド時に検索 store を作り、クライアント JavaScript がブラウザで検索する構造が自然だ。

問題は、検索結果カードまで JavaScript が直接作っていたことだった。

通常の一覧は _includes/archive-single.html が描画し、検索結果は assets/js/lunr/lunr-en.js が文字列をつなげて作っていた。

大まかにはこういう構造だった。

通常一覧
-> Liquid include
-> archive__item
-> date, category, views

検索結果
-> Lunr JS
-> hand-built HTML string
-> title and excerpt only

この構造が問題だった。

通常一覧にカテゴリバッジを追加しても、検索結果は変わらない。日付の表示ルールを直しても、検索結果は変わらない。閲覧数メタを調整しても、検索結果は変わらない。

同じ記事カードを二か所で別々に保守していた。

記事カードを共通 include に分離した

検索 JavaScript を大きくするのではなく、HTML を作らせない方向にした。

共通カード include を追加した。

_includes/archive-item-card.html
_includes/archive-item-meta-row.html
_includes/archive-item-categories.html

archive-item-card.html は文書を一つ受け取り、既存の一覧と同じ構造を描画する。

list__item
archive__item
archive__item-title
archive__item-meta-row
archive__item-excerpt

archive-item-meta-row.html は日付、閲覧数、カテゴリを一行にまとめる。日付と閲覧数は既存の page__meta.html を使い、カテゴリは archive-item-categories.html に分けた。

そして _includes/archive-single.html はカード HTML を直接持たない薄い wrapper にした。

{% include archive-item-card.html document=post type=include.type context="archive" %}

通常一覧も検索結果も、同じ include が作ったカード HTML を使う。

Lunr store に完成済みカード HTML を入れた

検索自体は引き続き Lunr が行う。

ただし検索結果 HTML は JavaScript が作らない。

Jekyll のビルド時に assets/js/lunr/lunr-store.js で各文書のカード include を描画し、その結果を html フィールドに入れた。

title
excerpt
categories
tags
lang
locale
html
url
teaser

検索 JavaScript は entry.html を取り出して挿入するだけになった。

以前は検索 JS の中にタイトルリンク、teaser 画像、抜粋の切り詰め、HTML 文字列の組み立てが混ざっていた。

var renderSearchResult = function (entry) {
  return entry.html || '';
};

検索 JS の責務は、検索、locale フィルタ、結果件数表示、結果挿入に絞った。

検索結果の閲覧数は表示だけで集計しない

検索結果にも閲覧数を出すべきか。

最初は外すことも考えた。検索結果がうるさくなる可能性があり、動的に挿入された DOM に閲覧数を入れるタイミングも見る必要があった。

ただ、通常一覧に合わせるなら閲覧数だけ抜けるのも不自然だった。

そこで検索結果にも閲覧数を表示することにした。ただし表示と集計は分けた。

Search result exposure is not a visit.
The view count in search results is display-only.
Actual visit tracking belongs only to the currently opened page.

このブログの閲覧数要素は data-page-view-path を持つ。実際の集計対象は、現在のページにある data-page-view-track="true" の要素だ。

検索結果カードは data-page-view-path だけを持ち、data-page-view-track="true" は持たない。

つまり検索結果に記事が表示されても、その記事の閲覧数は増えない。

数字だけを表示する。

動的な検索結果にも閲覧数を再適用する

検索結果はページ読み込み時点では DOM に存在しない。

ユーザーが検索語を入力した後に挿入される。閲覧数スクリプトがページ読み込み時に一度だけ要素を集める構造だと、検索結果内の閲覧数は埋まらない。

そこで assets/js/custom/visitor-stats.js も変更した。

ファイル上部で document.querySelectorAll("[data-page-view-path]") を固定せず、閲覧数を描画するたびに再取得するようにした。

analytics payload もキャッシュした。

latestAnalyticsPayload

検索結果の描画後、検索スクリプトは hyuk:search-results-rendered イベントを発行する。

visitor stats 側はこのイベントを受け取り、すでに payload があれば新しい DOM に閲覧数だけを再適用する。

API は再呼び出ししない。

検索入力が変わるたびに閲覧数 API を呼ぶと、検索 UI が閲覧数システムに干渉する。検索結果は頻繁に変わるので、ネットワークリクエストを毎回増やさない。

流れはこうした。

Page load
-> receive analytics payload once
-> render views for existing listing/current page

Search results render
-> dispatch event
-> reuse cached payload for newly inserted DOM

これで検索結果がカウンターを汚さない。

多言語検索メタも locale ごとに合わせた

多言語ブログ作業 の後、このサイトには /en//ja//zh-Hans/ のような locale ページと collection がある。検索も locale が混ざってはいけない。

検索 store には langlocale を入れ、カード HTML も文書 locale に基づいてメタラベルを出す。英語では Views、日本語では 閲覧数 のように表示される。

カテゴリも同じだ。

locale prefix 自体がカテゴリバッジに出てはいけないので、archive-item-categories.html では locale segment を飛ばし、実際のカテゴリだけを表示する。

検索オーバーレイにサイドバーと広い結果幅を戻した

カードを合わせただけでは終わらなかった。

検索を開くと結果幅が妙に狭かった。通常一覧と同じカード HTML を使っているのに、画面では右側の空間を使えていなかった。

さらに大きな問題もあった。検索を開くとサイドカテゴリも消えた。

検索オーバーレイは既存本文の上に重なる構造ではない。検索を開くと .initial-content を隠し、別の #site-search 領域を表示する。

そのため通常ページ内の sidebar も一緒に消える。共有カードを使っても、周囲のレイアウトが違えば検索画面は別物に見える。

そこで _includes/search/search_form.html にも sidebar を入れた。

<div class="search-content__inner-wrap">
  {% include sidebar.html %}
  <div class="archive search-content__archive">
    ...
  </div>
</div>

結果領域も archive search-content__archive で包み、検索結果が archive レイアウト内で流れるようにした。

カード幅を狭めていた CSS も外した。

Minimal Mistakes には検索用にこの規則があった。

.search-content .archive__item {
  @include breakpoint($large) {
    width: 75%;
  }

  @include breakpoint($x-large) {
    width: 50%;
  }
}

古く見えた理由はカード HTML だけではなかった。大きい画面では結果カード自体が半分の幅にされていた。

custom SCSS では検索結果カードが全幅を使うようにした。

.search-content .archive__item {
  width: 100%;
}

もう一つレイアウト上の問題があった。

通常の archive は右サイドバー用に padding-inline-end を持つ。検索オーバーレイには右サイドバーがない。この padding を残すと検索結果がまた狭くなる。

そこで検索オーバーレイの archive だけ右 padding を消した。

.search-content .search-content__archive {
  padding-inline-end: 0;
}

カード描画ルールは通常一覧と共有し、オーバーレイの配置だけ検索画面に合わせた。

デスクトップとモバイル幅まで確認した

ビルドと構文チェックを走らせた。

bundle exec jekyll build
node --check _site/assets/js/lunr/lunr-store.js
node --check _site/assets/js/lunr/lunr-en.js
node --check _site/assets/js/custom/visitor-stats.js

生成された検索 store には page__viewsdata-page-view-path が入った。

検索カード HTML には data-page-view-track="true" が入っていなかった。これが入ると検索結果への表示と訪問集計が混ざる。

その後、各 locale ページで検索を開き、次のような検索語を入れた。

References
Keymory
Daily review

結果カードでは日付、カテゴリ、閲覧数が通常一覧と同じメタ行に表示された。ラベルも Views閲覧数 など locale に従った。

検索結果内には data-page-view-track="true" がなかった。実際の記事ページには、従来どおり現在ページ用の tracking element が一つだけあった。

検索オーバーレイには sidebar も戻った。デスクトップ幅 1280px では sidebar 項目が 22 個あり、検索結果カード幅は約 974px まで広がった。

モバイル幅 390px では sidebar が上に積まれ、結果カードは 345px 幅でタイトル、メタ行、抜粋を表示した。

ブラウザコンソールのエラーもなかった。

コメントする