Next.JS App RouterにおいてSVGをAPIから出力する方法

SVGを最近よく活用している。CanvasよりReactとの相性はよいし、ベクターなのも好み。 (Three.jsを使えると面白いけど、、その機会はなかなかない。)

だいたいの場合は、HTMLに埋め込んで使えばいいので、特別なことをする必要はない。 ただ、たまに画像として保存したり、書き出したりしたいことがある。PPT用素材として使うとか、コンテンツとして独立させて、Markdownドキュメントから読み込ませるなど。

そういったケースにも対応するため、Next.JSのApp Router環境において、SVGをサーバサイドで生成して画像として提供する方法を探していたのだが、やっと解決策が見つかった。

以下のような感じ。

/app/api/svg/route.tsx

import React from "react";

function MySvg() {
  return (
    <svg version="1.1"
        baseProfile="full"
        width="300" height="200"
        xmlns="http://www.w3.org/2000/svg">
      <rect width="100%" height="100%" fill="red" />
      <circle cx="150" cy="100" r="80" fill="green" />
      <text x="150" y="125" font-size="60" text-anchor="middle" fill="white">SVG</text>
    </svg>
  )
}

export async function GET() {
  const ReactDOMServer = (await import('react-dom/server')).default
  const body = ReactDOMServer.renderToStaticMarkup(<MySvg />);

  return new Response(
    body, {
    status: 200,
    headers: {
      "Content-Type": "image/svg+xml",
    },
  });
}

これで/api/svgへアクセスすると、MySVGの内容がブラウザに表示される。

ChartGPT 4に相談すると

import { renderToStaticMarkup } from 'react-dom/server'

を教えてくれた。助かる。

ただ、そのままimportすると、

Error: 
  × You're importing a component that imports react-dom/server. To fix it, render or return the content directly as a Server Component instead for perf and security.

セキュリティの問題なのか、エラーがでてしまった。 githubながめてたらdynamic importで対応できるようだった。

  const ReactDOMServer = (await import('react-dom/server')).default

staticなsvg生成の範囲では問題ないと思うのだが、使用する場合はリスクもしっかり検討する必要はありそうではある。

関連する試みとしては、過去にPNGAPI経由で提供するケースも検討した。 ma38su.hatenablog.com