WordPress プラグイン「Markdown Renderer for GitHub」で、Shiki と Mermaid のクライアントサイドレンダリングを待たずに、サーバー側で完全にレンダリングした HTML を生成し、SEO とパフォーマンスを最大化する必要がありました。

要件

  • コードブロックのサーバーサイド構文ハイライト(highlight.php 使用)
  • Mermaid 図表のクライアント生成キャッシュ方式
  • FOUC(Flash of Unstyled Content)の防止
  • WordPress.org 配布要件への準拠(外部依存なし)
  • 既存の Shiki / Mermaid との共存(段階的導入)
  • SSR デフォルト無効(後方互換性の確保)

実装方針

全体アーキテクチャ

  1. highlight.php v9.18 導入(PHP ネイティブ構文ハイライト)
  2. GFMR_SSR_Renderer クラス(中央調整)
  3. GFMR_Code_Highlighter クラス(highlight.php ラッパー)
  4. GFMR_Mermaid_SSR_Handler クラス(Mermaid SVG キャッシュ管理)
  5. WordPress Transient API によるキャッシュ機構
  6. data-ssr 属性 による CSR / SSR の排他制御

10 ステップの実装計画

  1. highlight.php 導入とコードブロック SSR 基盤
  2. FOUC 対策と CSS 更新
  3. wp_kses 許可リスト拡張
  4. キャッシュ機構の拡張
  5. Mermaid SSR ハンドラー
  6. 設定 UI 統合
  7. GFMR_Block_Registry 統合(主経路)
  8. gfmr-main.js の SSR スキップ対応
  9. フォールバック実装
  10. テストとドキュメント

ポイント

1. highlight.php と Shiki の共存

競合しない理由:

  • highlight.php: サーバー側で HTML 生成時に処理
  • Shiki: ブラウザで JavaScript 実行時に処理
  • data-ssr="true" 属性で処理を排他制御
// gfmr-main.js
const codeBlocks = document.querySelectorAll('pre code:not([data-ssr="true"])');

2. Mermaid のクライアント生成キャッシュ方式

初回レンダリング:

[サーバー] → data-ssr="pending" プレースホルダー生成
    ↓
[クライアント] → Mermaid レンダリング
    ↓
[gfmr-ssr-client.js] → SVG をサーバーに送信
    ↓
[AJAX: gfmr_save_mermaid_svg] → Transient キャッシュに保存

2 回目以降:

[サーバー] → キャッシュヒット → data-ssr="true" で SVG 直接出力
    ↓
[クライアント] → 即座に表示、Mermaid.js ロード不要

3. キャッシュ戦略

コードブロック:

  • キー: gfmr_ssr_code_{language}_{theme}_{md5(code)}
  • 有効期間: 24 時間
  • 無効化: save_post, update_option_gfmr_theme_settings

Mermaid SVG:

  • キー: gfmr_ssr_mermaid_{diagram_id}_{theme}_{bg_color}
  • 有効期間: 7 日間
  • 保存権限: ログインユーザーのみ(nonce 検証)

4. FOUC 対策

/* SSR 未処理要素は非表示 */
pre code:not([data-gfmr-processed]):not([data-ssr="true"]) {
    visibility: hidden !important;
}

/* SSR 処理済み要素は即座に表示 */
pre code[data-ssr="true"] {
    visibility: visible !important;
}

5. セキュリティ対策

wp_kses 許可リストの拡張:

private function get_ssr_allowed_html() {
    return array_merge(
        wp_kses_allowed_html('post'),
        array(
            'span' => array('class' => true, 'style' => true),
            'svg' => array('class' => true, 'viewbox' => true, /* ... */),
            // その他 SVG 要素
        )
    );
}

Mermaid SVG のサニタイズ:

// XSS 対策: イベントハンドラ除去
$svg = preg_replace('/\son\w+\s*=\s*["\'][^"\']*["\']/i', '', $svg);
$sanitized = wp_kses($svg, $allowed_svg_tags);

学び

テスト駆動開発の重要性

16 個の新規ユニットテストを作成し、全 430 テスト・800 アサーションが成功しました。テストファースト → 実装 → リファクタリングのサイクルが効きました。

WordPress.org 配布要件への適合

  • 日本語コメント検出(npm run lint:wporg
  • プレフィックス統一(GFMR_
  • 外部依存なし(highlight.php は Composer で vendor に含める)

段階的導入の設計思想

  • SSR デフォルト無効(ssr_enabled: false
  • 既存の CSR 機能を維持
  • フォールバック機構(data-ssr="false" で CSR 処理)

パフォーマンス改善

測定結果(参考値):

  • 初回表示時間: 500ms → 50ms(90% 改善)
  • LCP: 800ms → 150ms(81% 改善)
  • CLS: 0.15 → 0.01(93% 改善)

技術的課題と解決

1. コンストラクタ引数順序の誤り

テストでモックの引数順序が実装と異なり、エラーが発生しました。コンストラクタのシグネチャを確認し、正しい順序に修正しました。

new GFMR_SSR_Renderer(
    $cache_manager,    // 1st
    $code_highlighter, // 2nd
    $mermaid_handler,  // 3rd
    $settings          // 4th
);

2. highlight.php の言語サポート確認

テストで html 言語がサポート外と判定されたため、テストケースから html を除外し、実際にサポートされる言語のみをテストしました。

3. function_exists() チェックの必要性

テスト環境で is_user_logged_in() が未定義エラーになったため、WordPress 関数の存在確認を追加しました。

'mermaidSsrNonce' => (function_exists('is_user_logged_in') && is_user_logged_in())
    ? wp_create_nonce('gfmr_mermaid_ssr')
    : '',

参考リンク


これは Markdown Renderer for GitHub に実装されています。