👾

Next.js(App Router)のServer Actionsを使って再検証処理まで実装してみる

に公開

Next.js(v13)のキャッチアップをしたので、その学習ログをまとめていきたいと思います。
今回は、サーバーアクションについて検証したので、少しでも参考になれば嬉しいです。

サーバーアクションとは?

概要

Server Actions は Next.js のバージョン 13.4 で新たに追加された機能です。

サーバーコンポーネント・クライアントコンポーネントの両方でサーバー上で実行される関数を呼び出すことが可能になり、これによってAPI エンドポイントを作成する必要がなくなりました。

https://nextjs.org/docs/app/api-reference/functions/server-actions

サーバーアクションの有効化

next.config.jsでserverActionsをtrueに設定することで使用が可能になります。

next.config.js
module.exports = {
  experimental: {
    serverActions: true,
  },
}

呼び出し

サーバーアクションは、サーバーコンポーネントとクライアントコンポーネントの両方から呼び出しが可能です。

🙆‍♂️ サーバーコンポーネントから呼び出す場合

src/app/page.tsx
export default function ServerComponent() {
  async function myAction() {
    'use server'
    // ...
  }
}

🙆‍♂️ クライアントコンポーネントから呼び出す場合

src/app/actions/index.ts
'use server'
 
export async function myAction() {
  // ...
}
src/app/client-conponent.tsx
'use client'
 
import { myAction } from './actions'
 
export default function ClientComponent() {
  return (
    <form action={myAction}>
      <button type="submit">Add to Cart</button>
    </form>
  )
}

🙅‍♂️ この場合はNGです。

src/app/client-conponent.tsx
'use client'
 
export default function ClientComponent() {
	async function myAction() {
    'use server'
    // ...
  }
  return (
    <form action={myAction}>
      <button type="submit">Add to Cart</button>
    </form>
  )
}

サイズ制限

デフォルトで設定されてる最大サイズは1MBであり、必要に応じてサイズの変更が可能です。

module.exports = {
  experimental: {
    serverActions: true,
    serverActionsBodySizeLimit: '2mb',
  },
}

サンプルコード

以下のコードは、簡易的な投稿ページの実装をしています。

ここでは概要については省略しますが、shadcn/ui(Tailwind CSS)を使ってスタイリングをしています。

https://ui.shadcn.com/

全体コード

src/app/posts/page.tsx
import { Button } from "@/feature/shadcn/ui/button"
import { Input } from "@/feature/shadcn/ui/input"
import { Label } from "@/feature/shadcn/ui/label"
import { Textarea } from "@/feature/shadcn/ui/textarea"
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/feature/shadcn/ui/card"
import { revalidateTag } from "next/cache"

type PostProps = {
  id: number
  title: string
  description: string
}

const Page = async () => {
  const res = await fetch("http://localhost:3032/posts", {
    method: "GET",
    cache: "no-cache",
    next: {
      tags: ["posts"],
    },
  })
  const posts: PostProps[] = await res.json()

  const addPost = async (formData: FormData) => {
    "use server"
    const title = formData.get("title")
    const description = formData.get("description")

    const res = await fetch("http://localhost:3032/posts", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        title: title,
        description: description,
      }),
    })
    revalidateTag("posts")
  }

  return (
    <div className="w-full">
      <h1 className="text-[18px] font-bold mb-4">投稿ページ</h1>
      {/* 投稿フォーム */}
      <div className="w-[500px] mb-8">
        <form action={addPost}>
          <div className="flex flex-col gap-1 mb-4">
            <Label>タイトル</Label>
            <Input name="title" />
          </div>
          <div className="flex flex-col gap-1 mb-4">
            <Label>説明文</Label>
            <Textarea name="description" />
          </div>
          <div className="w-full justify-center items-center text-center">
            <Button className="w-[200px]">投稿</Button>
          </div>
        </form>
      </div>
      {/* 投稿一覧 */}
      <div className="flex flex-row flex-wrap gap-8">
        {posts.map((post, index) => {
          return (
            <Card key={`post${index}`}>
              <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
                <CardTitle className="text-sm font-medium">{post.title}</CardTitle>
              </CardHeader>
              <CardContent>
                <div className="text-md font-bold">{post.description}</div>
              </CardContent>
            </Card>
          )
        })}
      </div>
    </div>
  )
}

export default Page

再検証

再検証とは、データ キャッシュを消去し、最新のデータを再フェッチするプロセスです。

データが変更され、最新の情報を取得したい場合に使用します。

以下は参考記事です。

https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#revalidating-data

revalidateTagを設定してあげることで、特定のキャッシュデータを更新することが可能になります。

const res = await fetch("http://localhost:3032/posts", {
    ...
    next: {
      tags: ["posts"],
    },
  })
const addPost = async (formData: FormData) => {
    ... 
    revalidateTag("posts")
  }

感想

Next.jsの成長スピードが早すぎてキャッチアップするのが大変だけど、Next.jsオンリーでできる幅が増えてきるので、嬉しい点でもあります。

次のリリースは不明ですが、どんな機能が追加されるのか発表が楽しみです!

おまけ

弊社では、スマホやPC1つで完結する網羅的な教材と、無制限で解ける本番と同形式の模試で、短期間での資格取得を目指すことができる簿記のアプリ 『Funda簿記』 を運営しています。

少しでも興味のある方がいれば、リンクよりアクセスしていただくか、メールにてお願いします☺️

https://boki.funda.jp/

Discussion