LoginSignup
1
0

Next.js のクライアントコンポーネントはクライアント側だけで動くわけではない

Last updated at Posted at 2024-02-06

svg.js を使って DOM をグリグリ触るコンポーネントを作り、それを Next.js 環境で動かしてみたところ、エラーが発生。そこから脱出するまでに時間がかかりました。
その時のお話をしたいと思います。

作っていたもの

svg.js というライブラリを使って SVG で画像を組み立てるものを作っていました。
DOM を操作するので、"use client" 指定をして、対象のコンポーネントはクライアントコンポーネントとなるようにしました。

発生したエラー

Cannot read property 'createElementNS' of undefined

このエラーがサーバー側で発生しました。

"クライアントコンポーネントなのになぜサーバー側でエラーが発生するんだ?"

発生した理由

Next.js のドキュメントに次の記述があります。

最初のページロードを最適化するために、Next.jsはReactのAPIを使用して、クライアントコンポーネントとサーバーコンポーネントの両方について、サーバー上に静的なHTMLプレビューをレンダリングします。つまり、ユーザーがアプリケーションに最初にアクセスしたとき、クライアントがクライアント コンポーネントのJavaScriptバンドルをダウンロード、解析、実行するのを待つことなく、すぐにページのコンテンツが表示されます。

--- DeepLによる翻訳

つまりクライアントコンポーネントとはいえ、サーバー側でもレンダリングされるのです。
そしてその中にクライアント側に依存するコードがある場合は、サーバー側でエラーが発生してしまうのです。

このことを教えてくれたのはstack overflowでした

この記事の場合は localStorage が問題でしたが、僕の場合は DOM 操作が問題だったようです。

解決方法

先程の記事では、こういったエラーを起こさないようにするには、クライアント側特有の操作をするコードを、useEffect の中に置くと良い、ということでしたので svg.js の Svg オブジェクトを生成する部分をuseEffect に移動したらたしかにその部分で発生していたエラーは出なくなりました。

しかし他の部分では、useEffect内に移動すると別な理由でうまく動作しないところがあり、再び詰まってしまいました。

"サーバー側でややこしいことせんでええから、純粋にクライアント側だけで動かす方法ないんか?"

とぼやいていたら、
ありました。

最終的な解決方法 Lazy Loading

Next.js の Lazy Loading で、コンポーネントを遅延ロードすることができますが、その際に {ssr: false} のオプションを渡してあげると、純粋なクライアント側コードとして扱われます。つまりサーバー側ではレンダリングされません。

import Editor from './Editor';

としていた部分を

import dynamic from 'next/dynamic';

const Editor = dynamic(() => import('./Editor'), { ssr: false });

という風に書き換えると、Editor コンポーネントは遅延ロードされ、サーバー側ではレンダリングされなくなります。これですべてのコンポーネントで、エラーが発生することはなくなりました。

このことを教えてくれたのは、"Common Errors in Next.js and How to Resolve Them" というブログ記事でした。

このエラーを解決するにはさまざまなアプローチがありますが、単純な選択肢のひとつは、ブラウザのウィンドウ・オブジェクトを必要とするコード・ブロックを実行するためにreactのuseEffect()フックを使用し、ページ・コンポーネントがマウントされたときだけコードが実行されるようにすることです。

もうひとつの方法は、ブラウザのウィンドウを必要とするコード部分をスタンドアロンコンポーネントに変換し、Next.jsのダイナミックインポート機能を使ってページコンポーネントにインポートすることです。Next.jsのダイナミックインポートは、コンポーネントを遅延ロードまたはオンデマンドで動的にロードするための機能です。ただし、この機能を使用するときに、サーバーレンダリングを有効または無効にできる追加のssrオプションが含まれています。

ssr値をfalseに設定するだけで、ブラウザの windowdocument に依存するコンポーネントや外部パッケージをロードできるようになります。

--- DeepLによる翻訳

わかれば簡単なことでしたが、最終的な解決法見つけるまでに時間がかかってしまいましたので、他の方の参考になるかと思い記事にしました。

追記: MUI を使っているなら NoSsr コンポーネント

MUI を使っているなら、<NoSsr> コンポーネントでラップしてあげるだけで、サーバー側でのプリレンダリングは行われなくなります。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0