メインコンテンツへスキップ
受託受付中相談する

Editorial newsの実装メモ — Astro 5で組んだ8つの仕組み

LeapLensのお知らせ (news) は、editorialメディアとして必要な機能を一通り備えている。 個別の機能はライブラリで足せても、それぞれの設計判断こそが資産になる。 この記事は、8つの主要な仕組みについて、なぜそれを選んだかをまとめておく。

1. 検索 (Pagefind)

ビルド時に dist/news/**/*.html をスキャンし、検索インデックスを dist/pagefind/ に出力する。 JSは約60KB(gz)で、検索結果のリンクは即時表示される。

<article data-pagefind-body> をお知らせ詳細ページに付与し、関連記事 / 共有 / navが結果に紛れない。

2. 関連記事

src/lib/news.ts の getRelatedPosts(posts, currentId, limit) で実装。

const scored = posts
  .filter((p) => p.id !== currentId)
  .map((p) => ({ post: p, score: p.data.tags.filter((t) => currentTags.has(t)).length }))
  .filter(({ score }) => score > 0)
  .sort((a, b) => b.score - a.score || newest);

共通タグ数でスコアし、同点なら新着順。タグが無い記事は空配列。

3. 記事内TOC

render(entry) が返す headings から depth 2-3 を拾って自動生成。#${slug} でアンカー移動。

4. prev / nextナビ

getAdjacentPosts(posts, currentId) で「ひとつ前/ひとつ次」を返す。最古 / 最新で片側null。 noUncheckedIndexedAccess を有効化しているので posts[i ± 1] ?? null でundefinedを排除。

5. 共有ボタン

X (post intent) / Hatena (entry/panel) / Copy Linkの3つ。Copyは navigator.clipboard.writeText() で1.5秒 “Copied!” 表示。

6. 読了時間

CJKと英単語で重み付けした独自計算: CJK 600字/分、英1単語 ≒ 2.4 unit。 混在文書でも自然な分数になる。

7. 構造化データ

ページごとに以下を出力:

  • Organization + WebSite (BaseLayoutがデフォルトで)
  • Article (keywords + wordCount)
  • BreadcrumbList
  • ItemList (一覧 / タグページ)

加えて og:type=article + article:published_time / modified_time / section / tag のOG article namespace、Microformats h-entryも併用。

8. 動的OG画像

astro-og-canvas でsite / news / lps / tagsの4系統 にPNG生成。 フォントはInter (英数) + Noto Sans JP (CJK) のスタック。 キャッシュは node_modules/.cache/og-images に置き、再ビルドのcold→warmが約700ms短縮される。


これらは個別には小さい仕組みだが、組み合わさると「読み手にとってのeditorial体験」になる。 1つでも欠けると違和感が出るので、初期に詰め込んだ判断は今のところ正解だったと思う。

なお、i18n (ja/en) は当初検討したが、辞書設計の運用コストとフラッグシップが日本語ターゲットである現状を鑑みて撤回した。背景は別記事「i18n辞書を1ファイルに集約する設計判断」を参照。