Next.js で dynamic import を使い Client だけで動かす Component を実現する
Next.js でサーバを動作させる場合、 App Router か Pages かに関わらず、 SSR がデフォルトの挙動となっているため、 App Router で 'use client'
pragma を指定しても SSR される。
そのため、 window に触れる、script タグで読み込んだ widget に useEffect で触れるなど、クライアントでしか動かない処理を含む Component が入っていると正常に動作しない。
そこで回避策として next/dynamic を利用した dynamic import を使用することで、 SSR 時には読み込ませず、 Client でのみ読み込ませるコンポーネントを実現する。
この手段は Client でしか動かない Component の他、データの取得に時間がかかるのでサーバ側での処理はスキップしたい(SSR の結果が返却されたのちに Client 側で fetch を開始したい)場合などにも有効なテクニックである。
'use client'
function ClientOnlyComponent () {
return (
// ...
{console.log('これはブラウザに出力される')}
<p>これはブラウザでのみ動作している</p>
//...
)
}
export default ClientOnlyComponent
import dynamic from 'next/dynamic'
import DynamicClientOnlyComponent = dynamic(import('./ClientOnlyComponent', { ssr: false }))
export function Page() {
return (
//...
{console.log('これは Next プロセスのログに出力される')}
<DynamicClientOnlyComponent />
//...
)
}
default export せずに dynamic(import('./ClientOnlyComponent').then((module) => ClientOnlyComponent)
のように named export された Component を参照しようとすると、 Error: Cannot access .then on the server. You cannot dot into a client module from a server component. You can only pass the imported name through.
というランタイムエラーが発生してしまう。(App Router の場合のみの挙動の可能性が高い)
そのため、 default export を使用している。
また、 Server Component との親子関係にも注意しなければならない。server only の Component は 'use client'
や dynamic import の子要素には配置できない。
余談
なぜこのようなことをする必要が出てきたかというと、 Twitter の埋め込み用のコードは script
タグから読み込む必要があり、それを利用している react-twitter-embed
は Client でしか動かないからである。
このあたりのコードで実装を変更している。
Discussion
良い記事ありがとうございます!
記事の骨格に関わることで申し訳ないのですが、こちらに記載の挙動はpages dirでも同じかと思います。pages dirではすべてのページがビルド時にレンダリングされます。対処も同じで、dynamic importを使います。
せっかく良い記事なので、もし余力があれば説明を少し直されると良いのではと思いました!
※私が仕様を誤解している可能性もあるため、修正する場合ドキュメントの確認や検証をしていただけると良いかと思います。
(ご認識のとおり、)これはClient Componentでも同じとなります。
また、pagesも同じ挙動です。
つまり、本質的にServer ComponentとSSRの挙動は関係ないということになります。
そのため、例えば「App Routerでも、React Server ComponentかClient Componentかに関わらずSSRがデフォルトとなっているため」といった表現が考えられます。
default exportも不要にする方法をpages dirでは検証しているので、後ほどシェアしますね!よければapp dirでも確認してみていただけると!これ、App Router固有のエラーが出ているということですね!こちらで検証してみます!
コメントありがとうございます!言われてみると pages でもそうであったので、タイトルと冒頭の趣旨を修正しました!
default exportじゃないとエラーが出るの、こちらでも確認しました!
ドキュメント上はnamed exportできるってことになっているので、バグかもしれませんね。
Discussionが1つ見つかりましたが、動きはなさそうでした...!