はじめに
Next.jsのApp Routerでサーバーコンポーネントを使用している際に、Reactコンポーネントを静的なHTML文字列に変換したいケースがあります。
例えば、動的な文書生成やメール本文の作成などの用途です。
renderToStaticMarkupとは
renderToStaticMarkup
は、React DOMのreact-dom/server
モジュールが提供する関数で、Reactコンポーネントを純粋なHTML文字列に変換します。
通常のReactレンダリングで生成されるDOM属性(data-reactroot
など)を含まない、軽量なHTMLを生成するために使用されます。
renderToString
との違いは、renderToStaticMarkup
がReact固有の属性を含まないため、サイズが小さく、クライアントでのハイドレーションが不要な場合に適しています。
問題: サーバーコンポーネントでの直接インポートはエラーになる
サーバーコンポーネントでは、react-dom/server
モジュールを直接インポートすることができません。
例えば、以下のようなコードはエラーになります。
// ❌ これはエラーになる
import { renderToStaticMarkup } from 'react-dom/server';
export default function MyServerComponent() {
const html = renderToStaticMarkup(<div>Hello World</div>);
return <div>{html}</div>;
}
実行すると、次のようなエラーが表示されます。
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.
Learn more: https://nextjs.org/docs/app/building-your-application/rendering
react-dom/serverをインポートするコンポーネントをインポートしています。これを修正するには、パフォーマンスとセキュリティのために、コンテンツを直接Server Componentとしてレンダリングまたは返すようにしてください。詳細はこちら:https://nextjs.org/docs/app/building-your-application/rendering
解決策: 動的インポートを使用する
この問題を解決するために、動的インポートを使用する方法があります。
// test-render.tsx
export const renderToHTML = async (): Promise<string> => {
const ReactDOMServer = await import("react-dom/server");
const html = ReactDOMServer.renderToStaticMarkup(<button>hello</button>);
return html;
};
この動的インポートを使用した関数は、Next.jsのServer ActionsやAPIルートなど、サーバーサイドで実行される様々な場所から呼び出すことができます。
解説
この解決策が機能する理由は以下のとおりです。
- サーバーコンポーネントの制約: Next.jsのサーバーコンポーネントでは特定のモジュール(この場合は
react-dom/server
)に直接アクセスできない制限があるようです。 - 動的インポート:
await import()
を使うことで、実行時にモジュールをロードでき、静的な依存関係チェックを回避できます。
注意点
- この方法はサーバーサイドでのみ動作します(ブラウザでは動作しません)
- 実行時のパフォーマンスを考慮する必要があります(動的インポートにはオーバーヘッドがあります)
- Next.jsのバージョンアップによって動作が変わる可能性があるため、最新の公式ドキュメントも参照してください
まとめ
Next.jsのサーバーコンポーネントでrenderToStaticMarkup
を使用したい場合は、動的インポートを使用することで実現できます。この方法を使うことで、Reactコンポーネントをサーバーサイドで静的なHTMLに変換し、さまざまな用途に活用することができます。
参考
Unable to import react-dom/server in a server component #43810