📑

【Next.js app/】Client Component で気をつけたいこと3選

2023/10/25に公開

1 - 'use client'を使う

コンポーネントをクライアント化する方法は2種類あります。

No.1: ファイルの冒頭に'use client'をつける

ファイルの冒頭に'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>
    )
}

仮に行った場合、つぎのエラーが出ます。

image.png

4 - Pre-Renderingされる

Client コンポーネントはサーバーサイドで一旦レンダリングされます。

公式ドキュメントに以下のような記載があります。

image.png

たとえば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