• #nuxt-content
  • #search
  • #MDC
  • #AST
  • #bug-fix
  • #FlexSearch
未分類

Nuxt Content検索機能のMDC AST対応修正

問題

検索ページで以下の問題が発生していました:

  • 「SEO」で検索: タイトルに含まれているのに0件
  • 「赤字」で検索: 本文に何度も出現しているのに0件

一部のキーワード(「Excel」「繰越欠損金」など)は検索できるが、他のキーワードは検索できない状態でした。

原因調査

1. FlexSearchの設定確認

最初は FlexSearch の tokenize: 'forward' が短い単語に対応していないと考え、tokenize: 'full' に変更しました。

  • ✅ 「SEO」が検索可能になった
  • ❌ 「赤字」はまだ0件

2. 本文抽出の問題を発見

デバッグログで kurikoshi-kessonkin-calculation-module の記事を調査:

{
  bodyLength: 115,  // わずか115文字!
  bodyPreview: "                   ",  // 空白ばかり
  has赤字InBody: false
}

本文が正しく抽出されていないことが判明しました。

3. AST構造の違いを発見

さらに調査を進めると、ASTの構造が想定と異なることが判明しました:

// 想定していた形式(従来のAST)
{
  type: 'text',
  value: 'テキスト内容'
}

// 実際の形式(MDC配列形式)
["h1", {id: "excelで繰越欠損金..."}, "Excelで繰越欠損金を自動計算!..."]

Nuxt ContentはMDC (Markdown Components) 形式でASTを構築しており、配列形式 ["tag", {attributes}, ...children] を使用していました。

既存の extractTextFromAST 関数は従来の AST 形式しか想定しておらず、MDC 配列形式のノードからテキストを抽出できていませんでした。

修正内容

extractTextFromAST 関数の改善

3種類のノード形式に対応するよう修正しました:

// ASTからテキストを再帰的に抽出
function extractTextFromAST(nodes) {
  if (!nodes || !Array.isArray(nodes)) return ''

  return nodes.map(node => {
    // 1. 文字列ノードの場合
    if (typeof node === 'string') {
      return node
    }
    
    // 2. 配列ノード(MDC形式: ["tag", {attrs}, ...children])の場合
    if (Array.isArray(node)) {
      // 最初の要素はタグ名、2番目は属性、3番目以降が子要素
      const children = node.slice(2)
      return extractTextFromAST(children)
    }
    
    // 3. オブジェクトノード(従来のAST形式)の場合
    if (node.type === 'text') {
      return node.value || ''
    }
    if (node.children) {
      return extractTextFromAST(node.children)
    }
    
    return ''
  }).join(' ')
}

対応したノード形式

形式説明
文字列"テキスト"直接のテキストコンテンツ
MDC配列["h1", {id: "..."}, "見出し"]Nuxt ContentのMDC形式
オブジェクト{type: 'text', value: '...'}従来のAST形式

結果

修正後の本文抽出結果:

{
  bodyLength: 3874,  // 115 → 3874文字に改善!
  bodyPreview: "Excelで繰越欠損金を自動計算!...",
  has赤字InBody: true  // ✅ 検出成功
}

検索結果の改善

  • 「SEO」検索: 5件ヒット(FlexSearch の tokenize: 'full' 効果)
  • 「赤字」検索: 1件ヒット(本文抽出の修正効果)
  • キーワードハイライト: スニペット内で正しく強調表示

学んだこと

1. Nuxt Content の AST 形式

Nuxt Content は MDC (Markdown Components) を使用しており、AST は配列形式 ["tag", {attrs}, ...children] で構築されます。従来の {type, value, children} 形式とは異なります。

2. デバッグの重要性

  • 症状: 一部のキーワードだけ検索できない
  • 仮説1: FlexSearch の設定問題 → 一部解決
  • 仮説2: 本文抽出の問題 → 根本原因を発見

段階的なデバッグログの追加が問題の特定に有効でした。

3. 柔軟な実装

複数の形式に対応することで、将来の変更にも強い実装になりました:

  • MDC配列形式(現在の主要形式)
  • 従来のAST形式(後方互換性)
  • 文字列ノード(エッジケース対応)

関連ファイル

  • apps/web/app/pages/search.vue:177-205 - extractTextFromAST 関数

参考