← ブログ一覧
サブ記事

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が "&quot; に変換する

Astroは {...} の式の出力を自動でHTMLエスケープする。これはXSS対策のための安全な仕様。

つまりJSON.stringifyの出力の中にある " が、HTMLとして出力されるときに全部 &quot; に変換される。

<!-- 期待していたHTML -->
<script type="application/json" id="loto-chart-data">
  {"chartLabels":["1","2","3",...]}
</script>

<!-- 実際に生成されたHTML -->
<script type="application/json" id="loto-chart-data">
  {&quot;chartLabels&quot;:[&quot;1&quot;,&quot;2&quot;,...]}
</script>

タクヤ「あ……&quot; ってHTMLエンティティですよね。これをJSON.parseしようとしたら壊れる」

リナ社長「そういうこと。{ の次に & が来てるから”property nameがない”ってエラーになってたわけ」

タクヤ「なるほど、position 8ってのは {&quot;& の位置だったんですね」

リナ社長「理解が早い!」


解決策: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対策で自動エスケープされる"&quot; になってJSONが壊れる
  • set:html でエスケープをスキップできる → サーバー側で生成した安全なデータだけに使う
  • グラフが表示されないとき → まずコンソールのエラーとHTMLソースを確認する癖をつけよう

タクヤ「コンソールのエラーをちゃんと読む大切さを学びました」

リナ社長「そこ大事!エラーをそのままClaudeに貼ったら一瞬で原因教えてくれるし、怖がらず確認していこ」


ミニロト解析ページ作成の全体まとめ記事に戻る

コメント