【Next.js app/】Client Component で気をつけたいこと3選
1 - 'use client'を使う
コンポーネントをクライアント化する方法は2種類あります。
'use client'
をつける
No.1: ファイルの冒頭にファイルの冒頭に'use client'
をつけることで、Client Componentになります。最もベーシックな設定方法だと思います。
// app/ClientComponent.tsx
'use client'
export default function ClientComponent(){
return (
<button onClick={()=>{
console.log('Hello, Client')
}>BUTTON</button>
)
}
// app/page.tsx
import ClientComponent from './ClientComponent.tsx'
export default function Page(){
return(
<div>
<ClientComponent />
</div>
)
}
No.2: 'use client'の付いているファイルに通過させる
コンポーネントのファイル自体に'use client'
がついていなくても、'use client'
がついている別のファイルにインポートすることで、Client化します。
// app/ClientComponent.tsx
export default function ClientComponent(){
return (
<button onClick={()=>{
console.log('Hello, Client')
}>BUTTON</button>
)
}
// app/client.ts
'use client'
export { default } from './ClientComponent.tsx'
// app/page.tsx
import ClientComponent from './client.ts'
export default function Page(){
return(
<div>
<ClientComponent />
</div>
)
}
この仕様のおかげで、たとえば外部ライブラリのコンポーネントがClient Component対応していなくても、適当なファイルに'use client'
をつけてインポート/エクスポートすることで、Client Componentとして扱えます。
しかし、注意も必要で、次で説明します。
2 - Server Component を直接ネストしない
Server Componentを直接Client Componentのファイルにインポートしてネストすると、Server ComponentがClient Componentとして扱われます。(さきほどNo2で示した仕様が理由で)
これを防ぐために、Client Component側に {children} を設定して、 Client Componentのオープンタグで包んであげましょう。
// ❌
// app/ServerComponent.tsx
export default function ServerComponent(){
return(
<div>Hello, Server</div>
)
}
// app/ClientComponent.tsx
'use client'
import ServerComponent from './ServerComponent.tsx'
export default function ClientComponent(){
return (
<button onClick={()=>{console.log("Hello, Client")}>
<ServerComponent />
</button>
)
}
// app/page.tsx
import ClientComponent from './ClientComponent.tsx'
export default function Page(){
return(
<div>
<ClientComponent />
</div>
)
}
// ⭕️
// app/ServerComponent.tsx
export default function ServerComponent(){
return(
<div>Hello, Server</div>
)
}
// app/ClientComponent.tsx
'use client'
export default function ClientComponent(){
return (
<button onClick={()=>{console.log("Hello, Client")}>
{ children }
</button>
)
}
// app/page.tsx
import ClientComponent from './ClientComponent.tsx'
import ServerComponent from './ServerComponent.tsx'
export default function Page(){
return(
<div>
<ClientComponent>
<ServerComponent />
</ClientComponent>
</div>
)
}
必ずオープンタグで囲わないといけないわけではありません。しかし、サーバーサイドで生成できるものをクライアント側に持っていきたくはないですよね。
3 - インデックスファイルからモジュールをインポートしない(できない)
Client Componentにはindex.ts/js
というファイルからインポートすることができません。
// CAN NOT DO THIS
// lib/index.ts
export const sayHello = () => {console.log('Hello')
// app/ClientComponent.tsx
'use client'
import { sayHello } from '../lib'
export default function ClientComponent(){
return (
<button onClick={()=>{
sayHello()
}>
You can't do this with ERROR
</button>
)
}
仮に行った場合、つぎのエラーが出ます。
4 - Pre-Renderingされる
Client コンポーネントはサーバーサイドで一旦レンダリングされます。
たとえばdocument
オブジェクトを使うような関数をコンポーネント内部で誤って使ってしまったとします。
// getDataTheme.ts
export const getDataTheme = () =>
document.documentElement.getAttribute('data-theme')
// Button.tsx
'use client'
import {getDataTheme} from './getDataTheme'
export default function Button(){
return (
<div>{getDataTheme()==='dark'?'Moon':'Sun'}</div>
)
}
すると、次のような警告がでると思います。
ReferenceError: document is not defined
これはクライアント側のAPI をサーバー側で使っているために引き起こされると考えられます。何かしらの方法で回避しましょう。
Client Componentを作成する際の知見をまとめました。お役に立てれば幸いです。
以上です。
Discussion