🐍

【Next.js】Next 難しい😇サーバーコンポーネントベースに作っていくために一つ、ヒントを知った😍

に公開
2

どもども、てるし〜です。
みなさん、少し遅くなりましたがあけましておめでとうございます。
昨年は私の記事を読んでくださりありがとうございました。
2025年もどうぞよろしくお願いいたします。

はじめに

2025年になり新しいプロジェクトへのアサインが決まりました。
現在仕事ではベースプロジェクト作成兼技術調査をしています。

技術スタックとしてはNext.jsを使います。

2023年とかにApp routerというものが現れRSC(React Server Component)というものが出てきてから数年が経ちましたが私は知らないことが多く仕事中に「え〜〜〜!初めて知ったわw」というものがよく出てきます。

現在はv15ですが、頑張ってキャッチアップをしていかないと、ですね!

今回のテーマは「Next.jsでサーバーコンポーネントをベースに作っていくために」に関する記事です。

そもそもRSCとは

RSCとはReact Server Componentの略でアーキテクチャーです。

React Server Components (RSC) は、ビルド時専用コンポーネント、サーバ専用コンポーネント、そしてインタラクティブなコンポーネントを、単一の React ツリーで混在させることができます。

https://ja.react.dev/learn/build-a-react-app-from-scratch#improving-application-performance

クライアントコンポーネントの中身(コンテンツ)をサーバーコンポーネントにしたいとき

今回の目玉はこちらです。

「常識だろ!」っていう人もいるとは思いますし、「え、知らなかった」っていう人もいると思います。
開発の参考にしていただけたらと思います。

具体例

私はよく仕事でポップアップを作るのでそれを例に見ていきましょう。

例えば上記のようなポップアップを作りたいとします。ポップアップの中身は静的なコンテンツになっています。また、一部静的なコンテンツが含んでいるものと捉えても良いと思います。

このポップアップの骨格は状態が変わる毎に出現させたり見えなくしたりするのでクライアントコンポーネントである必要はありますが、中身は静的なコンテンツを含んでいるのでサーバーコンポーネントを使いたいな〜となります。

それを実現するためにはどうすれば良いのでしょうか??

ベースにする記事・リポジトリ

おっと、ベースとなる記事とリポジトリを教えていませんでしたね🤪

https://zenn.dev/terusi/articles/79470ad2efe4c0

https://github.com/ShionTerunaga/popup-sample

上記を使おうとします。

ざっとおさらい

上記の記事はuseReduceruseContextを使って、どのページからでもポップアップを呼び出せるというものでした。

さらにはカスタムhookを作りより簡単にポップアップを開いたり閉じたりできるというような作りにしていたと思います。

実際にポップアップを開く関数は、

const openPopupClosingInButton = (children: ReactNode) => {
    dispatch({
        type: "show",
        children: children
    })
}

で呼び出し方としては

openPopupClosingInButton(<SampleContents1 />)

のように呼び出します。

①クライアントコンポーネント上でサーバーコンポーネント上をインポートする

では、早速ポップアップを呼び出してみたいと思います。
ポップアップを呼び出すボタンpopup-open-button.tsxというものを作りました。

popup-open-button.tsx
"use client"

import { usePopup } from "@/hooks/popup"
import { ReactNode } from "react"
import styles from "./style.css"

export const PopupOpenButton = () => {
    const { openPopupClosingInButton } = usePopup()

    const handleClick = () => {
        /*<SampleContents2 /> はRSC*/
        openPopupClosingInButton(<SampleContents2 />)
    }

    return (
        <button className={styles.button} onClick={handleClick}>
            開く
        </button>
    )
}

このコンポーネントは"use client"がついているのでクライアントコンポーネントです。
そこでサーバーコンポーネントであるSampleContents2というポップアップの中身をインポートして宣言してあげてみます。

試しにSampleContents2にログを入れて確認してみましょう。

import { PopupCloseButton } from "@/components/popup-close-button"
import styles from "./style.css"

export const Sample2 = () => {
    console.log("お腹すいた。ラーメン食べたい🍜")

    return (
        <div className={styles.container}>
            <div className={styles.box}>
                <h1>サンプル2</h1>
                <p className={styles.text}>
                    これはサンプル2のポップアップです。
                </p>
                <div>
                    <PopupCloseButton />
                </div>
            </div>
        </div>
    )
}

ログを仕込んだので実際の動きを見てみます。

ポップアップが開いた瞬間ログが出てきました。
これはブラウザ上でJSが実行されている証拠です。

つまり目的はRSCでしたがRCCになってしまったので達成されませんでした😭

②サーバーコンポーネントからクライアントコンポーネントへchildrenで渡す

残念なことにクライアントコンポーネントでサーバーコンポーネントを呼び出すとクライアントコンポーんと以下のものも全てクライアントコンポーネントになリます。

では中身をpropsのchildrenとして渡してみましょう。
まずpopup-open-button.tsxを改造します。

"use client"

import { usePopup } from "@/hooks/popup"
import { ReactNode } from "react"
import styles from "./style.css"

interface props {
    children: ReactNode
}

export const PopupOpenButton = (props: props) => {
    const { openPopupClosingInButton } = usePopup()

    const handleClick = () => {
        openPopupClosingInButton(props.children)
    }

    return (
        <button className={styles.button} onClick={handleClick}>
            開く
        </button>
    )
}

そしてそれを呼び出している部分も変更します。

<PopupOpenButton>
    <SampleContents2 />
</PopupOpenButton>

では同じようにログを見てみましょう。

おっと、serverというものが追加されましたね。

これはNext.js15からの機能で、サーバーコンポーネントでもブラウザでログが見れるようになりました!!!

サーバーコンポーネントからクライアントコンポーネントをインポートする際に、サーバーコンポーネントであるコンテンツをchildrenで渡すことでコンテンツがサーバーコンポーネントとして描画されました!!

目的達成!!!

つまり

簡単に言います。

  • クライアントコンポーネントでインポートする時はサーバーコンポーネントであろうがクライアントコンポーネントであろうがクライアントコンポーネントになる
  • サーバーコンポーネントからクライアントコンポーネントをインポートする際に中身のサーバーコンポーネントをchildrenで渡せば意図通りサーバーコンポーネントとしてコンテンツを表示できる!!

まとめ

今回サーバーコンポーネントベースで作って行くためにポップアップを例にヒントを得ました。
ちなみに、みなさんはこのことはご存知だったでしょうか?

これを知ると次にぶつかる壁は設計をどうしよう。。。となっていくはずです。

Next.jsの設計は結構難しいと思います。

サーバーコンポーネントをベースにするために他にもuseStateをバケツリレー。。ではなくcontextを使って〜〜とかとかポイントはたくさんありそうです。

久々に記事を書きましたが、いかがだったでしょうか??

今回の記事も読んでいただきありがとうございます。

それではまた👋

Discussion

Teruhisa - T6ADEVTeruhisa - T6ADEV

こんにちは、些細なことですが1点気になったのでコメントを残しておきます。
React Server Components、RSCとはアーキテクチャの名称です。その中で、コンポーネントの種類が「サーバーコンポーネント」と「クライアントコンポーネント」として区別されます。

そのため、クライアントコンポーネントを文中にあるような「React Client Component」 や、「RCC」などと呼称しないので注意が必要です。(同様にサーバーコンポーネントのこともReact Server ComponentやRSCとは呼びません)

てるし〜てるし〜

コメントありがとうございます。

勘違いしていたようで穴があったら入りたくなりました😇

ドキュメントにもちゃんと書いてありましたね。(最初から読めという話ですが)
https://ja.react.dev/reference/rsc/server-components

余談ですが、アーキテクチャなら
やかましい名前なので「React server component architecture」で略称が「RSCA」とかなら勘違いしなかったかもですね。(的外れなこと言ってたらすみません。)