Server Componentで作成したフォームをChakra UIで簡単にリッチにする #techtekt Advent Calendar 2023

はじめに

 パーソルキャリアに今年入社しました23卒エンジニアの熊切です。普段はSalariesで主にフロントエンドエンジニアを担当しております。今回は今年話題になったApp RouterのServer Componentについて触れようと思います。

 App RouterはNext.js 13.4でstableとなった新たなルーター機構を指します。Server ComponentはApp Routerのベースとなる技術です。新しい技術として注目を集めていますが、その特性とMUI等のUIライブラリの相性が良くないため(理由は後述)、Server ComponentとUIライブラリの両方の利点を活かした開発は難しい状況でした。

 そんな中UIライブラリの一つであるChakra UIのGitHub Issue内に以下のような重要な一文が投稿され、話題になりました。

ChakraUIGitHub
https://github.com/chakra-ui/chakra-ui/issues/7649#issuecomment-1769503921

 これを踏まえ本記事では以下の事項について触れていきたいと思います

  • Server ComponentとUIライブラリ
  • 実際に動かしてみた

Server ComponentとUIライブラリ

Server Componentとは

 まずそもそもServer Componentとは何かについて触れていきたいと思います。内容はドキュメントを要約したものですので、詳しくはこちらをご覧ください。

 Server Componentは言葉のとおりサーバーサイドでレンダリングされるコンポーネントです。なおクライアントサイドでレンダリングされるコンポーネントはClient Componentと呼ばれます。  これまでデフォルトだったクライアントサイドでレンダリングされる場合と比較し以下のような利点があります。

  • データソースに近い位置でのデータフェッチ
  • サーバー内でデータやり取りが完結するためセキュリティがより堅牢になる
  • bundleサイズの削減
  • StreamingとSuspenseによる並列したデータ読み込み

 特にbundleサイズの削減はServer Componentの量だけクライアントで読み込むjavascriptが削減されるため大きなメリットであると言えます。

SSRとServer Componentの特徴

 少し脱線してしまいますが、一度ここでSSRとServer Componentの特徴についても整理したいと思います。2つを比較した際に各技術を一言で表現するなら以下のようになるかなと個人的には考えています。

  • SSR:初期ページを素早く表示する技術
  • Server Component:bundleサイズを削減できる技術

 SSRはHTMLをサーバー側で作成し、必要なjavascriptは全てクライアント側で読み込みHydrationすることで動作可能なアプリケーションとなります。HTMLが用意された状態でサーバー側から送られてくるため初期表示が速くなります。

 ここでSSRとServer Componentの組み合わせを考えます。Server Componentはbundleサイズを削減してくれるので、SSRだと全て読み込んでいたjavascriptも削減します。そのため2つを組み合わせると初期表示が早く、読み込むjavascriptの量を減らしてくれるようになります。またSSRのみでデータを取得し表示しようとすると、逐次処理のためデータ取得時間分表示までに時間がかかってしまいました。しかしServer ComponentはStreamingとSuspenseにより各コンポーネントごとに並列のローディングができるようになったため、早く表示しつつ時間がかかるコンポーネントだけローディング表示することでUXの向上に繋げることが可能になりました。

 これらのことからSSRとServer Componentの組み合わせで、これまでと違った開発やUXの提供が可能になりました。

Server ComponentとUIライブラリの関係性

 脱線してしまったので話を戻します。Server Componentはたくさんの利点があるのですが、はじめに言及したとおりMUI等のUIライブラリとの関係性は未だ良くありません。ここではその理由について触れたいと思います。

 多くのUIライブラリではUXや開発体験の向上のためCSS in JSをベースに使用しています。多くのCSS in JSはクライアントサイドで実行されるのですが、これまで述べてきたとおりServer Componentのjavascriptはクライアントサイドでは実行されないため齟齬が発生してしまいます。そのためドキュメントにもUIライブラリを使用するためには多くの場合'use clinet'ディレクティブを使用しクライアントコンポーネントを用いないとダメと記載してあります。その関係性を今回打ち砕いたのがChakra UIです。

実際に動かしてみた

 新しくNext.jsのプロジェクトを作成しChakra UI x Sever Componentのみでフォームの作成を行おうと思います。

App Routerプロジェクト作成~Chakra UIインストール

 まずNext.jsの新規プロジェクトを以下のコマンドで作成します。色々聞かれますが全部Enterを押下します。手順通りであればApp Routerで導入されます。

npx create-next-app@latest

 続いてChakra UIをドキュメント通りにインストールします。CSS in JSのライブラリであるemotionですが、インストールしないと依存関係の都合で動作しなかったため今回はドキュメント通りにインストールしました。

yarn add @chakra-ui/react @chakra-ui/next-js @emotion/react @emotion/styled framer-motion

 最後にappディレクトリ直下のlayout.tsx内にChakraProviderを追加します。

import type { Metadata } from "next";
import { Inter } from "next/font/google";
import { ChakraProvider } from "@chakra-ui/react";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <ChakraProvider>{children}</ChakraProvider>
      </body>
    </html>
  );
}

Server Componentの動作確認

 動作確認のためsrcディレクトリ内にcomponentsディレクトリを作成し、ServerComponent.tsxを新たに追加します。App Router内で作成されたコンポーネントはデフォルトがServer Componentとなります。

import { Card, Button } from "@chakra-ui/react";

export async function ServerComponent() {
  console.log("Server Componentを実行しています");

  return (
    <>
      <Card bg="tomato">これはサーバーコンポーネントのChakra UIです</Card>
    </>
  );
}

 作成したコンポーネントをappディレクトリ直下のpage.tsxで読み込みます。ここでのポイントはファイル先頭に'use client'の宣言をしていないことです。'use client'によりそのファイル内で使用されるコンポーネントは子要素も含めClient Componentとなります。今回はどのファイルにも'use client'を宣言していないため全てServer Componentとなります。

import { ServerComponent } from "../components/ServerComponent";

export default function Home() {
  return (
    <main>
      <div
        style={{
          display: "flex",
          justifyContent: "center",
          alignItems: "center",
          marginTop: "300px",
        }}
      >
        <ServerComponent />
      </div>
    </main>
  );
}

 実際の出力が以下となります。期待した出力となっており実際に動作していることがわかります。

Server Componentの動作確認

また実際にSever Componentで動作しているかをconsoleで確認した画像も添付します。ServerComponent.tsx内ではconsole.logを使用しているのですがブラウザでは何も表示がありません。その代わりに動作させているサーバー側ではconsoleに出力されているためコンポーネントがServer Componentであることが確認できます。

Server Componentであるかの確認

フォームの作成

 Chakra UIがServer Componentで動作することが確認できたので次はフォームを作成します。

 まず簡単にフォームの見た目だけ整えようと思います。先ほど作成したServerComponent.tsxを以下のように変更します。コードの記載量はかなり少ないですが整ったフォームができます。

import {
  Button,
  FormControl,
  FormLabel,
  Input,
  Card,
  CardHeader,
  Heading,
  Box,
  CardBody,
} from "@chakra-ui/react";

export async function ServerComponent() {
  console.log("Server Componentを実行しています");
  
  return (
    <Card w={600} h={400}>
      <CardHeader>
        <Heading size="md">Server Component Form</Heading>
      </CardHeader>
      <CardBody w={500}>
        <form action={serverAction}>
          <FormLabel>Email address</FormLabel>
          <Input type="email" name="email" placeholder="hoge@fuga.com" />
          <FormLabel marginTop={30}>Name</FormLabel>
          <Input type="text" name="name" placeholder="name" />
          <Button
            type="submit"
            name="submit"
            marginTop={50}
            bgColor={"#6495ED"}
            color={"white"}
          >
            Submit
          </Button>
        </form>
      </CardBody>
    </Card>
  );
}

ChakraUIでスタイルを整える

 では実際に動くフォームを作成したいと思います。従来のClient Componentでのフォーム作成はuseState等のhooksを用いて実装するのが一般的でしたがServer Componentではこれまで述べてきた理由によりstate管理等ができません。しかしフォームについてもNext.jsのドキュメントに方法について記載があるのでそれに則り実装します。具体的にはServer Actionと呼ばれる関数を用いることで実現できます。先ほど変更したServerComponent.tsxにServer Actionを追記します。

// 省略
import axios from "axios";

export async function ServerComponent() {
  console.log("Server Componentを実行しています");

  const serverAction = async (params: FormData) => {
    "use server";
    // リクエスト内容をそのまま返すだけのAPIを叩く
    axios
      .post("http://localhost:8888", {
        email: params.get("email"),
        name: params.get("name"),
      })
      .then((res) => {
        console.log(res.data);
      });
  };

  return (
    <Card w={600} h={400}>
      {/* 省略 */}
        <form action={serverAction}>
          {/* 省略 */}

 今回はaxiosを用いてAPIを叩くようにしました。なおAPIサーバーはPOSTされた値をそのままレスポンスとして返すだけに設定しています。Server Actionのポイントとしては関数内の先頭に"use server"を指定することです。これによりServer Actionとして認識され非同期処理も可能となります。作成したServer Actionをformのactionで指定することで動作します。

 では実際に動作しているか確認してみます。

Server Actionの動作確認

 サーバー側のコンソールによりしっかりとデータが返ってきていることがわかります。Server ActionによりServer Componentでもデータのやり取りができることが確認できました。

 なお補足となりますが今回実装しなかったフォームバリデーションはzod等を用いることで実現可能なようなので運用でも問題なさそうです。

まとめ

 今回はChakra UIとServer Componentを用いて簡単でリッチなフォームを作成してみました。Server Componentとの欠点と思われていたことが新たな技術によりカバーされてきていることが実感できたのではないでしょうか。最近ですとPanda CSSのようなゼロランタイムCSSの流行もありますので今後もServer Component関連の技術からは目が離せません。

参考文献

 今回参考させていただいた記事を掲載します。大変わかりやすく勉強になるためぜひご参照ください。

熊切 俊夫 Toshio Kumakiri

エンジニアリング統括部 クライアントサービス開発部 SalariesエンジニアリングG

2023年4月に新卒でパーソルキャリアへ入社。主にフロントエンドエンジニアとしてサービス開発に従事。

※2023年12月現在の情報です。