Remix Viteで一部をクライアントサイドのみにする
import React, { useState, useEffect, ReactNode } from "react";
const App: React.FC = () => {
const [chart, setChart] = useState<ReactNode | null>(null);
useEffect(() => {
// クライアントサイド環境でのみ動的インポートを実行
if (typeof window !== "undefined") {
loadChart();
}
}, []);
// loadChart 関数を useEffect の外に移動
async function loadChart(): Promise<void> {
const Chart = (await import("react-apexcharts")).default;
const options = {
chart: {
id: "basic-bar"
},
xaxis: {
categories: [1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999]
}
};
const series = [
{
name: "series-1",
data: [30, 40, 45, 50, 49, 60, 70, 91]
}
];
setChart(<Chart options={options} series={series} type="bar" width="500" />);
}
return (
<div className="app">
<div className="row">
<div className="mixed-chart">
{chart || <p>Loading chart...</p>}
</div>
</div>
</div>
);
}
export default App;
}
問題:チャートの描画などのクライアントサイドのみ動作するライブラリを使うとwindow is not defined になる
Remixは半分はサーバーサイドなのでクライアントサイドで記述するのはクライアントサイドのみでやりたいときの方法です。
サーバーサイドレンダリング (SSR) を使用するフレームワーク、例えばRemixで開発している場合、window オブジェクトのようなブラウザ固有のグローバル変数がデフォルトで存在しないため、それらを利用するライブラリをサーバーサイドで実行しようとすると "window is not defined" のようなエラーが発生します。これは特に、チャートやビジュアルコンテンツを扱うJavaScriptライブラリがクライアントサイド専用である場合に顕著です。
エラーの解決方法
Remixやその他のSSRを利用するReactアプリケーションでは、クライアントサイド専用のコードを適切に管理する必要があります。以下は、そのような問題を解決するための一般的なアプローチです:
1.動的インポートと条件付きレンダリング:
クライアントサイドでのみ必要なライブラリは、動的にインポートすることでサーバー上でのコード実行を回避できます。また、useEffect などのReactフックを利用して、クライアントサイドでのみ特定のコードが実行されるようにすることができます。
useEffect(() => {
if (typeof window !== "undefined") {
loadChart();
}
}, []);
2.コンポーネントの遅延ローディング:
Reactの lazy と Suspense を使うことで、クライアントサイドでのみレンダリングされるべきコンポーネントの読み込みを遅延させることができます。これは特に大きなライブラリや多くの依存関係を持つコンポーネントに有効です。
3.サーバーとクライアントのコードの明確な分離:
サーバーで実行されることが絶対にないコードは、クライアントサイド専用のファイルやモジュールに分離することが推奨されます。これにより、ビルド時にクライアントとサーバー向けのコードを効果的に管理できます。
実装例
以下のコードは、react-apexcharts ライブラリを使用してチャートを表示するシンプルな例です。このコードは、クライアントサイドでのみ実行され、サーバーサイドでは何も行われません。
import React, { useState, useEffect, ReactNode } from "react";
const App: React.FC = () => {
const [chart, setChart] = useState<ReactNode | null>(null);
useEffect(() => {
if (typeof window !== "undefined") {
loadChart();
}
}, []);
async function loadChart() {
const Chart = (await import('react-apexcharts')).default;
const options = { /* チャートの設定 */ };
const series = [{ /* データシリーズ */ }];
setChart(<Chart options={options} series={series} type="bar" />);
}
return (
<div>
{chart || <p>Loading chart...</p>}
</div>
);
};
export default App;
このアプローチにより、サーバーサイドとクライアントサイドのコードの実行を効果的に制御し、SSR環境でのJavaScriptのエラーを防ぐことができます。
Discussion