• #Nuxt
  • #Nuxt Content
  • #Shiki
  • #シンタックスハイライト
  • #トラブルシューティング
  • #解決済み
未分類

シンタックスハイライトが効かない問題の調査と対応

問題の概要

マークダウンファイル内のコードブロック(例: TypeScript, HTML, CSSなど)にシンタックスハイライトが適用されず、すべてのコードが単色で表示される問題が発生。

症状

  • コードブロック内のキーワード、変数、文字列などが色分けされない
  • すべてのテキストが同じ色(デフォルトの黒)で表示
  • 構文に応じた色の変化がない

影響範囲

  • すべてのマークダウンファイル(apps/web/content/**/*.md
  • すべての言語(TypeScript, JavaScript, Vue, CSS, HTML など)

調査プロセス

1. 初期状態の確認

設定ファイル: apps/web/nuxt.config.ts

content: {
  build: {
    markdown: {
      highlight: {
        theme: {
          default: "github-light",
          dark: "github-dark"
        },
        langs: [
          "javascript",
          "typescript",
          "vue",
          "css",
          // ... その他の言語
        ]
      }
    }
  }
}

この設定は正しいパスのように見えた。

2. ブラウザでのHTML構造確認

Chrome DevTools MCPを使用して実際のHTML構造を調査:

// 実際のHTML出力
<pre class="language-html doc__code-block">
  <code>
    <span class="line">
      <span class="sVt8B">&lt;</span>
      <span class="s9eBZ">div</span>
      // ...
    </span>
  </code>
</pre>

発見事項:

  • <pre>要素にshikiクラスが付いていない
  • 代わりにlanguage-htmlというクラスが付いている
  • <span>要素にsVt8Bなどのクラスは付いている(Shikiのマークアップは生成されている)

3. CSS セレクタの確認

生成されているCSSルールを確認:

html pre.shiki code .sVt8B,
html code.shiki .sVt8B {
  --shiki-default: #24292E;
  --shiki-dark: #E1E4E8;
}

問題点:

  • CSSセレクタがpre.shikiを要求している
  • 実際の<pre>にはshikiクラスがない(language-htmlのみ)
  • そのため、CSSルールがマッチせず、スタイルが適用されない

4. CSS変数の確認

スタイル自体は存在するが、colorプロパティに適用されていない:

// 計算済みスタイル
{
  color: "rgb(36, 41, 46)",  // デフォルトの色
  backgroundColor: "rgba(0, 0, 0, 0)"
}

CSS変数(--shiki-defaultなど)は定義されているが、実際のcolorプロパティとして使用されていない。

試したアプローチ

アプローチ1: content.build.markdown.highlightcontent.highlight

仮説: Nuxt Content v3では設定パスが変更されている可能性

変更内容:

// 変更前
content: {
  build: {
    markdown: {
      highlight: { ... }
    }
  }
}

// 変更後
content: {
  highlight: { ... }
}

結果: ❌ エラーが発生

Package subpath './text' is not defined by "exports"

原因: langs配列に"text"を追加していたが、Shikiがサポートしていない


アプローチ2: text言語を削除して再試行

変更内容:

content: {
  highlight: {
    theme: {
      default: "github-light",
      dark: "github-dark"
    }
    // langs配列から"text"を削除
  }
}

結果: ❌ サーバーは起動するが、ハイライトは効かない

原因: <pre>要素に依然としてshikiクラスが付いていない


アプローチ3: 元の設定に戻す

変更内容:

// content.highlight から content.build.markdown.highlight に戻す

結果: ❌ 変わらず

根本原因

Nuxt Content v3では、Shikiが生成するHTMLマークアップとCSSセレクタに不一致が存在:

  1. 期待される構造 (CSSセレクタ):
    <pre class="shiki">
      <code>
        <span class="sVt8B">...</span>
      </code>
    </pre>
    
  2. 実際の構造:
    <pre class="language-html doc__code-block">
      <code>
        <span class="sVt8B">...</span>
      </code>
    </pre>
    
  3. CSS変数の適用不足:
    • CSS変数(--shiki-default, --shiki-dark)は定義される
    • しかし、color: var(--shiki-default)のような使用が行われていない

未解決の状態

現時点では以下の状態:

  • ✅ Shikiのマークアップ(<span class="sVt8B">など)は生成されている
  • ✅ CSS変数は定義されている
  • <pre>要素にshikiクラスが付いていない
  • ❌ CSSセレクタがマッチしない
  • ❌ シンタックスハイライトが表示されない

次のステップ候補

オプション1: カスタムCSSを追加

.language-*クラスでもハイライトが効くようにCSSを追加:

/* app.vue または global.css に追加 */
pre.language-html code .sVt8B,
pre.language-typescript code .sVt8B,
pre.language-javascript code .sVt8B,
/* ... 他の言語 */ {
  color: var(--shiki-default);
}

/* ダークモード */
@media (prefers-color-scheme: dark) {
  pre.language-html code .sVt8B,
  /* ... */ {
    color: var(--shiki-dark);
  }
}

メリット: 既存の設定を変更せず、CSSで対応 デメリット: 各クラス・各言語に対して手動でCSSを追加する必要がある

オプション2: Nuxt Content設定の見直し

公式ドキュメントを確認して、Nuxt Content v3の正しいハイライト設定を適用:

  • @nuxt/contentのバージョン: ^3.7.1
  • Shikiのデフォルト動作の確認
  • カスタムレンダラーの使用を検討

オプション3: highlight.jsへの切り替え

依存関係にhighlight.jsが含まれているため、Shikiの代わりにhighlight.jsを使用:

content: {
  highlight: false  // Shikiを無効化
}

その後、カスタムのhighlight.js統合を実装。

参考情報

使用しているパッケージバージョン

{
  "@nuxt/content": "^3.7.1",
  "nuxt": "^4.1.2",
  "highlight.js": "^11.11.1"
}

調査に使用したツール

  • Chrome DevTools MCP (mcp__chrome-devtools__evaluate_script)
  • Nuxt DevTools
  • ブラウザの要素検証ツール

関連ファイル

  • apps/web/nuxt.config.ts - Nuxt設定ファイル
  • apps/web/content/**/*.md - 影響を受けるマークダウンファイル
  • 生成されるHTML内のインラインCSS

アプローチ4: カスタムCSS追加(属性セレクタ使用)

仮説: pre.shikiの代わりにpre[class^="language-"]でセレクタを書けば、既存のCSS変数を利用できる

変更内容: apps/web/app/app.vue<style>セクションに以下を追加:

/* Shikiシンタックスハイライト対応: language-*クラスでもCSS変数を適用 */
pre[class^="language-"] code span[class],
pre[class*=" language-"] code span[class] {
  color: var(--shiki-default, inherit);
}

@media (prefers-color-scheme: dark) {
  pre[class^="language-"] code span[class],
  pre[class*=" language-"] code span[class] {
    color: var(--shiki-dark, inherit);
  }
}

実装日時: 2025-11-28

アプローチの詳細:

  • Shikiが生成するCSS変数(--shiki-default, --shiki-dark)はすでに各<span>要素に定義されている
  • しかし、それらをcolorプロパティとして使用するCSSルールがマッチしていない
  • pre[class^="language-"]language-で始まるクラス)という属性セレクタを使用することで、pre.shikiを期待するオリジナルのCSSルールの代わりに動作させる
  • すべてのShikiクラス(span[class])に対して一括で適用されるため、個別のクラスごとにルールを書く必要がない

結果: 🔄 部分的に効果あり(追加改善が必要)

問題点:

  • Shikiが生成するCSS変数定義ルールもpre.shikiセレクタを要求している
  • つまり、pre.shikiがないと、CSS変数自体が定義されない
  • カスタムCSSでvar(--shiki-default)を使っても、変数が未定義のため効果なし

改善策: アプローチ5に進む


アプローチ5: JavaScriptでshikiクラスを動的追加

仮説: <pre class="language-*">要素にshikiクラスを追加すれば、既存のShiki CSSルールがそのまま動作する

変更内容: apps/web/app/app.vue<script setup>セクションを追加:

import { onMounted } from 'vue'

// Shikiクラス追加: language-*クラスを持つpre要素にshikiクラスを追加
onMounted(() => {
  const addShikiClass = () => {
    const preElements = document.querySelectorAll('pre[class^="language-"], pre[class*=" language-"]')
    preElements.forEach(el => {
      if (!el.classList.contains('shiki')) {
        el.classList.add('shiki')
      }
    })
  }

  // 初回実行
  addShikiClass()

  // ルート変更時にも実行(Nuxtのクライアントサイドナビゲーション対応)
  const observer = new MutationObserver(addShikiClass)
  observer.observe(document.body, { childList: true, subtree: true })
})

実装日時: 2025-11-28

アプローチの詳細:

  • 根本原因は<pre>要素にshikiクラスが付いていないこと
  • JavaScriptでDOM操作によりshikiクラスを追加することで、既存のShiki CSSルール(pre.shiki code .sVt8Bなど)がマッチするようになる
  • MutationObserverを使用して、クライアントサイドナビゲーション後も動作を保証
  • 既存のShikiが生成するCSSをそのまま活用できるため、個別のクラスやCSS変数を定義する必要がない

結果: ✅ 成功

検証結果: Chrome DevToolsで確認した結果:

  1. <pre>要素にshikiクラスが正常に追加されている
    // 実際のクラス
    "language-typescript shiki doc__code-block"
    
  2. ✅ シンタックスハイライトが適用されている
    • 8種類の色で構文が色分けされている
    • 332個のspan要素すべてに適切な色が適用
  3. ✅ CSSルールがマッチしている
    • pre.shiki code .sVt8Bなどのセレクタが正常に動作
    • CSS変数が定義され、colorプロパティとして使用されている

JavaScriptコード: apps/web/app/app.vueに追加したコードが正常に動作:

onMounted(() => {
  const addShikiClass = () => {
    const preElements = document.querySelectorAll('pre[class^="language-"], pre[class*=" language-"]')
    preElements.forEach(el => {
      if (!el.classList.contains('shiki')) {
        el.classList.add('shiki')
      }
    })
  }

  addShikiClass()
  const observer = new MutationObserver(addShikiClass)
  observer.observe(document.body, { childList: true, subtree: true })
})

追加の改善: テーマの変更

初期テーマ(github-light / github-dark)がカラフルすぎて見づらかったため、よりシンプルなテーマに変更:

apps/web/nuxt.config.ts:

highlight: {
  theme: {
    default: "vitesse-light",
    dark: "vitesse-light"
  }
}

注意事項:

  • テーマを変更した場合は、開発サーバーを再起動する必要がある(Nuxt Contentはビルド時にCSSを生成するため)
  • 再起動せずにリロードしても、古いテーマの色が表示される

まとめ

問題の根本原因

シンタックスハイライトが効かない原因は、Shikiが生成するHTMLの<pre>要素にshikiクラスが付いておらず、CSSセレクタがマッチしないためです。

  • Shikiが生成するCSSルール: html pre.shiki code .sVt8B { ... }
  • 実際のHTML: <pre class="language-typescript doc__code-block">shikiクラスが無い)
  • 結果: CSSセレクタがマッチせず、スタイルが適用されない

解決策

アプローチ5: JavaScriptでshikiクラスを動的追加 ✅ 成功

apps/web/app/app.vueに以下のコードを追加:

<script setup lang="ts">
import { onMounted } from 'vue'

onMounted(() => {
  const addShikiClass = () => {
    const preElements = document.querySelectorAll('pre[class^="language-"], pre[class*=" language-"]')
    preElements.forEach(el => {
      if (!el.classList.contains('shiki')) {
        el.classList.add('shiki')
      }
    })
  }

  addShikiClass()
  const observer = new MutationObserver(addShikiClass)
  observer.observe(document.body, { childList: true, subtree: true })
})
</script>

メリット:

  • 既存のShikiが生成するCSSをそのまま活用できる
  • 個別のクラスやCSS変数を定義する必要がない
  • クライアントサイドナビゲーション後も動作する(MutationObserver使用)

テーマの変更: より見やすいテーマに変更(apps/web/nuxt.config.ts):

content: {
  build: {
    markdown: {
      highlight: {
        theme: {
          default: "vitesse-light",
          dark: "vitesse-light"
        }
      }
    }
  }
}

重要な注意事項:

  • テーマを変更した場合は、開発サーバーの再起動が必須
  • Nuxt Contentはビルド時にShikiのCSSを生成するため、再起動なしでは反映されない

今後同様の問題が発生した場合のチェックリスト

  1. ブラウザの開発者ツールで<pre>要素のクラスを確認
  2. 期待されるCSSセレクタと実際のHTMLが一致しているか確認
  3. CSS変数が定義されているか、colorプロパティとして使用されているか確認
  4. テーマ変更後は必ずサーバーを再起動