🏝

なぜReact Ariaコンポーネントをサーバーコンポーネントにおいて利用するとNext.jsでビルドエラーになるのか

2025/01/15に公開

React Ariaのコンポーネントをサーバーコンポーネントで利用すると、Next.jsではビルドエラーとなる。
具体的には、

import { Link } from "react-aria-components";

export function FooLink() {
  return <Link href="/foo">foo</Link>;
}

このようなコンポーネントがあるだけで以下のメッセージとともにビルドエラーとなる。

'client-only' cannot be imported from a Server Component module. It should only be used from a Client Component.

エラーメッセージの通り、React Ariaのコンポーネントがサーバーコンポーネントではimportできないことによって発生するもので、React Ariaのコンポーネント種別問わず全てのコンポーネントで同様に起きるエラー。
React Ariaのコンポーネントを利用する箇所でuse clientディレクティブを指定するなどしてクライアントコンポーネントとして扱われるようにすればビルドエラーを解消できるけど、その背景についての確認してみる。

client-onlyとは何か

まず、'client-only' cannot be imported...で始まる上述のエラーメッセージのclient-onlyとは何か。Next.jsのリポジトリーではpackages/next/src/compiled/client-onlyにあるパッケージのことを指しているはず。

パッケージ内は以下のファイルだけで構成されていて、

client-only/
  error.js
  index.js
  package.json

package.jsonを見るとConditional exportsで環境に応じてimportされるファイルを変えるようになっている。

package.json
"exports": {
  ".": {
    "react-server": "./error.js", // 中身はthrow new Error(...)のみ
    "default": "./index.js" // 中身は空っぽ
  }

webpackにおいてはresolve.conditionNamesによってカスタムのConditional exportsがサポート可能のようで、Next.jsの場合を例にとるとreact-serverのConditional exportsがその形で設定されているように見える。

従ってreact-serverのConditional exportsをサポートしている状況であれば、サーバーコンポーネントにおいてはerror.jsimportされ、それ以外の環境ではindex.jsimportされる。
index.jsが読み込まれるとその時点で例外が投げられることになる。

なお、Next.jsのソースコードの以下の箇所から該当のエラーメッセージを見つけることができる。

https://github.com/vercel/next.js/blob/c8251c303065e4f819b1e8aeed8019ef39bf5846/packages/next/src/build/webpack-config.ts#L1204-L1218

ここまでみてきた内容から、client-onlyは、サーバーコンポーネントでimportしたらビルドエラーにすることでモジュールの利用箇所をクライアントコンポーネントのみに制限するユーティリティーパッケージであることが分かる。

use clientディレクティブではないのはなぜか

エラーメッセージの内容自体は理解できるとしても、ビルドエラーが発生することは少し過剰な印象も感じる。

Reactの公式ドキュメントのサードパーティライブラリーの使用に関するセクションには以下の通り記載があり、サードパーティーのライブラリー側でuse clientディレクティブを含んでいれば、アプリケーション側ではビルド時にエラーとならず利用可能なことを期待したくなる。

ライブラリがサーバコンポーネントと互換性を有するように更新済みであれば、中に既に 'use client' マーカが含まれていますので、サーバコンポーネントから直接使用することができます。

https://ja.react.dev/reference/rsc/use-client#using-third-party-libraries

この点について、React Ariaの対応については以下のPull Requestからその背景が窺える。

https://github.com/adobe/react-spectrum/pull/5826

React Ariaも当初はuse clientディレクティブをつける対応を行なっていて、それをRevertしてclient-onlyを入れる対応へと路線変更したのがこのPull Request。
サーバーコンポーネントからクライアントコンポーネントへ受け渡すPropsには制限があり、イベントハンドラーのようなシリアライズできない値は受け渡せない。
そのため、イベントハンドラーを扱うことの多いReact Ariaのコンポーネントを利用する場合、React Ariaの内部でuse clientディレクティブを付けようが、結局アプリケーション側でクライアントコンポーネントを用意する必要が生じるということが変更の理由として挙げられている。

However in reality this did not work well because all of the props sent from a server component to a client component must be serializable. Therefore, things like event handlers could not be sent. Most of our components require some kind of event, so you'd end up needing to move the component that used RSP/RAC to your own client component anyway.

https://github.com/adobe/react-spectrum/pull/5826#issue-2121756169

上述のReactの公式ドキュメントのサードパーティライブラリーの使用に関するセクションにおいても、client-onlyについてではないけど、同様の記述がある。

ライブラリが更新されていない場合、あるいはコンポーネントが受け取る props にクライアントでのみ指定できるイベントハンドラのようなものが含まれている場合、サードパーティのクライアントコンポーネントとそれを使用したいサーバコンポーネントの間に、自分でクライアントコンポーネントファイルを追加する必要があるかもしれません。

https://ja.react.dev/reference/rsc/use-client#using-third-party-libraries

また、以下のissueではReact Ariaのコンポーネントがサーバーコンポーネントになり得なくてもSSRは可能である[1]ことや個々のコンポーネントに応じたビルドのカスタマイズは現状難しい[2]ことにも言及がありそう。

https://github.com/adobe/react-spectrum/issues/5876

全く別のライブラリーの話になるけどClerkは、use clientディレクティブはアプリケーション側の関心事であるとして、React Ariaと同様にclient-onlyを入れる対応を行なっている。

https://github.com/clerk/javascript/pull/3014

なお、React Ariaの類似ライブラリーの対応について知りたくなり@ark-ui/reactではどうなのかをみてみると、ファイル名からuse clientディレクティブの付与をハンドリングする関数がビルド時に呼ばれているように見える。

https://github.com/chakra-ui/ark/blob/be7cb1ed010fceabcd33600695c02630c945b8cd/packages/react/vite.config.mts#L76-L85

まとめ

ここまでをまとめると、なぜReact Ariaコンポーネントをサーバーコンポーネントにおいて利用するとNext.jsでビルドエラーになるのか、という疑問に関しては、

  • 大抵イベントハンドラーを受け渡すなどして、結局React Ariaを利用するアプリケーション側でクライアントコンポーネントにする必要が生じるから
  • 一部の例外のコンポーネント(例えば@adobe/react-spectrumFlex)のために現状のビルドを見直すのはコストバランスが見合わないから

といったあたりが現時点での答えになりそう。議論の余地があることはissueのコメント等から窺えるものの、現時点ではReact Ariaの対応方針がそうなっている。

use clientディレクティブを入れるかどうかは、今後もライブラリーによって対応方針が分かれるところなのかもしれない。

参考

脚注
  1. https://github.com/adobe/react-spectrum/issues/5876#issuecomment-1952850755 ↩︎

  2. https://github.com/adobe/react-spectrum/issues/5876#issuecomment-1969549641 ↩︎

GitHubで編集を提案

Discussion