• トップ
  • ブログ一覧
  • Next.jsのApp Routerで、ReactのServer ComponentとClinet Componentの境界を理解したい
  • Next.jsのApp Routerで、ReactのServer ComponentとClinet Componentの境界を理解したい

    1. はじめに

    ブログの目的

    • Server ComponentとClient Componentの境界を明確に理解すること。
    • サーバーコンポーネントとクライアントコンポーネントの境界を正しく定義できるようになること
      • Next.jsのApp RouterではデフォルトでコンポーネントがServer になります。Client Componentを使用する場合は、'use client' で境界を定義できます。 'use client'を正しい場所に設置できるようになることを目的とします

    なぜ境界を理解する必要があるのか

    パフォーマンス面

    サーバーコンポーネントとクライアントコンポーネントの境界を理解することで、どの処理をサーバー側で行い、どの処理をクライアント側で行うべきかを判断できます。

    たとえば、重い計算処理やデータの取得はサーバー側で行い、クライアントに必要最低限のデータを送信することで、クライアントの負荷を軽減し、全体的なパフォーマンスを向上させることができます。

    セキュリティ面

    サーバーコンポーネントを利用することで、重要なデータやビジネスロジックをサーバー側で保持し、クライアントには最低限のデータしか送信しないようにすることができます。これにより、機密情報がクライアント側で露出するリスクを低減し、データの安全性を確保できます。

    どこを境界とするかは、reactユーザーが決めることができます。

    クライアントとサーバーの境界を、どこに設定するべきなのかを背景やそのコンポーネントの用途を含めて理解していこうと思います。

    2. Server ComponentとClient Componentとは

    Server Componentとは

    バンドル前に事前にレンダリングされるコンポーネントです。

    ビルド時に一度だけ実行することも、ウェブサーバを使用してリクエストごとに実行することができます。

    Server ComponentはReactDOMServer APIを介して、Nodeサーバーなどのランタイム上で実行されます

    参考:サーバ用 React DOM API

    ブラウザにデータが送られないため、credentialsや機密情報がブラウザ側に漏れ出さずに実行できます。

    そのため、サーバーコンポーネントの用途は以下になります。

    Server Componentの用途
    • データをフェッチする
    • 機密情報を扱う
    • 静的なコンテンツをレンダリングする

    Server Componentがレンダリングするのは、RSCペイロードというデータです。

    Next.jsのApp Routerでは、このRSCペイロードとClient ComponentのJSを実行してHTMLをレンダリングします。

    Client Componentとは

    ブラウザ・サーバーで実行されるコンポーネントです。

    ブラウザ上で実行されるため、コンポーネントの状態管理やDOM操作、ブラウザのAPIを使用することができます。

    そのため、サーバーコンポーネントの用途は以下になります。

    Client Componentの用途
    • インタラクティブなコンテンツ
    • DOMの操作・イベントハンドリング・状態の管理など動的なコンテンツを扱う

    3.なぜ、Server と Clientで境界が必要なのか

    なぜこの境界が必要であるかを理解するためには、境界がないとき(Server Component 登場以前)のreactの課題を知る必要があります。

    Server Component登場以前は、現在のClient Componentと同じで、ブラウザ・サーバーの両方でJSが実行されていました。

    サーバーでのみ実行すべきJSも、バンドルされブラウザに送られてしまいます。

    サーバーでのみ実行すべきJS

    • ライブラリのインポート・パース
    • データのフェッチ

    課題

    ブラウザに全てのJSを送信してしまうと、ページの表示期間中に変化しない静的なコンテンツをただレンダリングするためだけに、ユーザはライブラリを追加でダウンロードしてパースし、さらにページのロード後に別のリクエストがデータを取得してくるのを待つ必要がありました。

    参考:server-components-without-a-server

    Server Componentによる課題の解決

    Server Componentを使用し、use clientでクライアント側の境界を指定することで、「どの部分のJSをバンドルしブラウザに送信するのか」ということをreactに教えることができるようになりました。

    具体的には、Server Componentでは、静的なコンテンツのレンダリング・データのフェッチを実行し、Client Componentではページ表示後のインタラクティブなコンテンツを表示するといった役割の分担が可能になります。

    Server Componentによってレンダリングされた静的なコンテンツが先にページ上に表示され、データのフェッチはサーバーで非同期に実行し、ユーザーは「静的なコンテンツをただレンダーするためだけに」、ページの表示を待つ必要がなくなります。

    なぜ、Server と Clientで境界が必要なのか の疑問に対しては、ブラウザで実行されるべきコンポーネントと、サーバーで実行されるべきコンポーネントをreact ユーザーが判別し、最適な形で、クライアント側にデータ・JSを送信するためという答えになります。

    4. 実際のコード例

    Next.jsのApp Routerを使用して、コンソール上に「ServerComponentです」というテキストを出力するServer Componentを作成します。

    /app/_components/ServerComponet.jsx

    1export const ServerComponent = () => {
    2  console.log('ServerComponentです')
    3  return (<div>Server Component</div>)
    4}

    作成したServer Componentをページで表示します。
    /app/sample_server_component/page.tsx

    1import { ServerComponent } from '../_components/ServerComponent'
    2const Page = () => {
    3  return <ServerComponent />
    4}
    5
    6export default Page

    sample_server_componentにアクセスして、コンソールの出力を確認

    http://localhost:3000/sample_server_component

    ブラウザ上のコンソールには何も出力されませんが、node上では出力されています。

    サーバーでのみJSが実行されていることがわかります。

    境界を指定する

    ServerComponentに、'use client'を記述し、サーバーコンポーネントとクライアントコンポーネントの境界を定義します。
    「ServerComponentという名前のClientComponentです」というテキストを出力します
    /app/_components/ServerComponent.jsx

    1'use cleint'
    2export const ServerComponent = () => {
    3  console.log('ServerComponentという名前のClientComponentです')
    4  return (<div>Server Component</div>)
    5}

    http://localhost:3000/sample_server_component にアクセスすると、ブラウザ上とコンソール上で「ServerComponentという名前のClientComponentです」というテキストが表示されました。

    ブラウザ

    コンソール上(サーバー)

    注意点

    サーバーコンポーネントで取得したデータを無闇にクライアントコンポーネントに渡さないようにする必要があります。

    <UserInfo />というクライアントコンポーネントにpropsとしてデータを渡します。

    1
    2export const ServerComponent = () => {
    3  const fetchUserData = () =>{ 
    4     return {name: "ユーザー名",  secret: 機密情報}
    5}
    6  const data = fetchUserData() 
    7  return <UserInfo data={data} />
    8}

    機密情報を含んだデータがClient Componentにpropsとして渡されています。

    これだと、propsのdataがブラウザに送られるので、secret:機密情報がブラウザに露出してしまいます。

    対応としては、クライアントコンポーネントで使用するデータを絞り、ブラウザに露出してはいけないものを、クライアントコンポーネントに渡さないようにしましょう。

    クライアントとサーバーの境界を理解し、どのデータをサーバーで扱い・どのデータをクライアント側で扱うのかを意識しながら実装することが大切です。

    5. まとめ

    サーバーコンポーネントとクライアントコンポーネントの境界を理解することは、reactを使用したWebアプリケーション開発において不可欠です。この境界を明確にすることで、パフォーマンスの最適化とセキュリティの強化が実現できます。

    パフォーマンスの観点

    サーバーコンポーネントを使用して初期表示速度を向上させ、サーバーでの重い処理を行うことでクライアントの負荷を軽減できます。一方、クライアントコンポーネントは、インタラクティブなユーザーインターフェースを提供し、ユーザー操作に即座に反応するため、リッチなユーザー体験を提供します。

    セキュリティの観点

    重要なデータやビジネスロジックをサーバー側で処理することで、クライアント側での情報漏洩リスクを低減できます。クライアントコンポーネントは、ブラウザで直接実行されるため、攻撃のリスクが高まりますが、適切な境界設定により、これらのリスクを最小限に抑えることが可能です。

    ライトコードでは、エンジニアを積極採用中!

    ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。

    採用情報へ

    やのけん(エンジニア)
    やのけん(エンジニア)
    Show more...

    おすすめ記事

    エンジニア大募集中!

    ライトコードでは、エンジニアを積極採用中です。

    特に、WEBエンジニアとモバイルエンジニアは是非ご応募お待ちしております!

    また、フリーランスエンジニア様も大募集中です。

    background