Astroで構築したサイトにおいて、<head>タグ内に記述したスクリプトがビルド後に消えてしまうという現象が発生することがあります。
Astroは「最終的に生成されるHTML/CSS/JSをできる限り軽量にする」という設計思想を持っており、そのために内部ではViteというビルドツールを利用しています。
このViteには「使われていないコードを削除する(ツリーシェイキング)」という最適化機能があります。これは、インポートされたJSモジュールやコンポーネントのうち、実際に参照されていないものを自動で検出し、最終的な出力から省くというものです。
本来これは「未使用コードを削ってファイルサイズを減らす」ための便利な仕組みですが、副作用として、次のようなことが起こります:
-
<head>タグ内に直接書いた<script>タグなどが、他のJSやテンプレートから明示的に参照されていないと「使われていない」と判断される - 結果として、実際には必要なスクリプトであっても、ビルド後にHTML出力から除外されてしまう
これは特に、計測タグ・外部ウィジェット・フォント読み込みなどの直接はどこからも参照されないが、HTMLに含めること自体が目的のコードにとって致命的です。
調べたところ、この問題に対する確実な対策として、Astroでは次のように記述すると良いようです:
<Fragment set:html={`<script src="https://example.com/script.js"></script>`} />
このようにすることで、スクリプトはHTML文字列として明示的に出力され、ビルド時の最適化処理の対象外となります。
<Fragment>とは
<Fragment> は Astro の特殊なタグで、実際には何のHTMLタグも生成しないラッパーです。Reactの <></>(フラグメント)と同様、構造上複数のノードを返したいときや、特定の属性を適用するためのダミー要素として使われます。
Astroでは特に、set:html でHTML文字列を差し込むための器として使用されることが多く、<Fragment> 自体が出力に現れることはありません。
set:htmlとは
set:html は、指定された文字列をそのままHTMLとして評価し、出力に埋め込むAstro独自の属性です。これはReactにおける dangerouslySetInnerHTML と同様に、エスケープ処理を行わず、与えられた文字列をそのままDOMに描画します。
このため、通常の<script>タグのようにパースされ、Viteのビルドプロセスに干渉されることなく、確実に出力HTMLに含めることができます。
まとめ
Astroでは、<script>タグを通常通りに書いても、Viteの最適化処理により削除されてしまう場合があります。これを防ぐために、スクリプトを文字列として差し込む方法として、<Fragment> + set:html の組み合わせが推奨されます。