AstroでJSONを埋め込んだらグラフが消えた——HTMLエスケープの罠
script type="application/json" に JSON.stringify を埋め込んだら " が " に化けてJSON.parseが失敗した。set:htmlで解決した話
登場人物
- リナ社長 … 高校生なのに会社経営するやり手ギャル。テックにも強くてAI活用が得意。
- タクヤ … 入社3年目の男性社員。真面目で少しだけコードが書ける。
タクヤ「社長、グラフが全部消えてるんですけど……コンソール見たらJSON.parseのエラーが出てます」
リナ社長「あ、それHTMLエスケープ問題じゃん。てかこれわかりにくすぎてまじでやばい」
タクヤ「HTMLエスケープ……?JSONと何の関係があるんですか?」
何が起きたのか
ミニロト解析ページではChart.jsのグラフに渡すデータをHTMLに埋め込んでいた。Astroのコードはこんな感じ:
<!-- Astroテンプレート側 -->
<script type="application/json" id="loto-chart-data">
{JSON.stringify({ chartLabels, chartFreq, chartRecentFreq })}
</script>
// クライアント側JavaScript
const chartData = JSON.parse(
document.getElementById('loto-chart-data').textContent
);
ローカルでも本番でもグラフが一切表示されず、コンソールにはこんなエラーが:
Uncaught SyntaxError: Expected property name or '}' in JSON at position 8
at JSON.parse (<anonymous>)
タクヤ「position 8でいきなりエラーって、JSONの先頭付近で何かがおかしいってことですよね」
リナ社長「そーそー。実際に生成されたHTMLを見てみると……」
原因:Astroが " を " に変換する
Astroは {...} の式の出力を自動でHTMLエスケープする。これはXSS対策のための安全な仕様。
つまりJSON.stringifyの出力の中にある " が、HTMLとして出力されるときに全部 " に変換される。
<!-- 期待していたHTML -->
<script type="application/json" id="loto-chart-data">
{"chartLabels":["1","2","3",...]}
</script>
<!-- 実際に生成されたHTML -->
<script type="application/json" id="loto-chart-data">
{"chartLabels":["1","2",...]}
</script>
タクヤ「あ……" ってHTMLエンティティですよね。これをJSON.parseしようとしたら壊れる」
リナ社長「そういうこと。{ の次に & が来てるから”property nameがない”ってエラーになってたわけ」
タクヤ「なるほど、position 8ってのは {" の & の位置だったんですね」
リナ社長「理解が早い!」
解決策:set:html でエスケープをスキップする
Astroには set:html というディレクティブがあって、HTMLエスケープをせずにそのまま出力できる。
<!-- ❌ Before(エスケープされてしまう) -->
<script type="application/json" id="loto-chart-data">
{JSON.stringify({ chartLabels, chartFreq, chartRecentFreq })}
</script>
<!-- ✅ After(set:htmlでエスケープをスキップ) -->
<script
type="application/json"
id="loto-chart-data"
set:html={JSON.stringify({ chartLabels, chartFreq, chartRecentFreq })}
></script>
タクヤ「set:html って初めて知りました。これはいつ使うんですか?」
リナ社長「HTMLをそのまま出力したいとき。今回みたいにJSONを埋め込むときとか、外部から取得したHTMLを表示したいときとか。ただしXSSの心配がないデータにだけ使うこと」
タクヤ「サーバー側で生成したデータなら安全ってことですね」
リナ社長「そーそー。ユーザーの入力をそのまま set:html に渡すのは絶対NG」
ポイントまとめ
リナ社長「まとめるとこれ」
- Astroの
{...}はXSS対策で自動エスケープされる →"が"になってJSONが壊れる set:htmlでエスケープをスキップできる → サーバー側で生成した安全なデータだけに使う- グラフが表示されないとき → まずコンソールのエラーとHTMLソースを確認する癖をつけよう
タクヤ「コンソールのエラーをちゃんと読む大切さを学びました」
リナ社長「そこ大事!エラーをそのままClaudeに貼ったら一瞬で原因教えてくれるし、怖がらず確認していこ」
コメント