[🛠] 検索結果を記事一覧カードに合わせる
✨ 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 には lang と locale を入れ、カード 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__views と data-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 幅でタイトル、メタ行、抜粋を表示した。
ブラウザコンソールのエラーもなかった。
コメントする