Next.js 13 server actionsの基本的な使い方
2023/09/06

Next.js 13 server actionsの基本的な使い方

6 mins to read

Next.js 13で発表されたserver actionsの基本的な使い方をまとめます。とりあえずどんなものなのか試してみたい方は参考にしてみてください

準備1: next.config.jsを編集する

experimentalのところにserverActions: trueを設定してserver actionsを有効にしておいてください

next.config.js
const config = {
  reactStrictMode: false,
  swcMinify: true,
  eslint: {
    ignoreDuringBuilds: true,
  },
  experimental: {
    appDir: true,
    serverActions: true, // ここ
  },
  images: {
    domains: [],
  },
};

export default config;

準備2: prismaについて

今回の例ではprismaを使用し、以下のようなモデルを使うことを前提としています。

model Post {
  id            String   @id @default(cuid())
  title         String
  content       String   @db.LongText
  createdAt     DateTime @default(now()) @map(name: "created_at")
  updatedAt     DateTime @default(now()) @map(name: "updated_at")
  @@map(name: "posts")
}

server actions (CRUD)

では、基本的なCRUD操作をserver actionsで行ってみます。

  • ファイルの最初に"use server;"と記述する
  • 関数の最初に"use server;"と記述する
  • 非同期関数(async)を使用する

というのがserver actionsの基本的な書き方です。
以下がCRUD処理をserver actionsを行う例です。

actions/post.ts
"use server";

import { prisma } from "@/lib/prisma";
import { revalidatePath } from "next/cache";

export const createPost = async ({ title, content }: any) => {
  "use server";
  const res = await prisma.post.create({
    data: {
      title,
      content,
    },
  });
  revalidatePath('/posts');
  return res;
};

export const updatePost = async ({ id, title, content }: any) => {
  "use server";
  const res = await prisma.post.update({
    where: {
      id: id,
    },
    data: {
      title,
      content,
    },
  });
  revalidatePath('/posts');
  return res;
};

export const deletePost = async ({ id }: any) => {
  "use server";
  const res = await prisma.post.delete({
    where: {
      id: id,
    },
  });
  revalidatePath('/posts');
  return res;
};

export const getPost = async ({ id }: any) => {
  "use server";
  const res = await prisma.post.find({
    where: {
      id: id,
    },
  });
  return res;
};

export const getPosts = async () => {
  "use server";
  const res = await prisma.post.findMany({
    orderBy: { createdAt: "desc" },
  });
  return res;
};

revalidatePathについて

revalidatePath関数は、サーバーでの処理が完了した後に引数に渡されたパス内で使用されているデータを再取得し、ブラウザ上に反映する為の関数です。

今回は/postsページ内でgetPostsを使って記事の一覧を取得し表示するので、引数に/postsを渡しています。サーバー側での処理(追加・更新・削除など)が完了した後、自動でブラウザに変更内容が反映されるようになっています。

revalidatePathを使わない場合は、記事の追加・変更・削除を行った後に手動でブラウザの更新ボタンを押すまでブラウザに表示されるデータが更新されません。

server actionsの呼び出し

フォームコンポーネント(create)

記事の新規追加を行うフォームコンポーネントです。

components/form.tsx
"use client";

import { createPosts } from "@/actions/post";

export function Form() {
  const submit = async (e: any) => {
    e.preventDefault();
    await createPost({ 
      title: e.target.title.value,
      content: e.target.content.value
    });
  }
  return (
    <form onSubmit={submit}>
      <input name="title" />
      <textarea name="content" />
      <button type="submit">登録</button>
    </form>
  );
}

ポストリストコンポーネント(update, delete)

記事の更新(updatePost)と削除(deletePost)を行っています。

components/post-list.tsx
"use client";

import { Form } from "@/components/form";
import { updatePost, deletePost } from "@/actions/post";

export function PostList({ id, title, content }) {
  const submit = async (e: any) => {
    e.preventDefault();
    await updatePost({ 
      id,
      title: e.target.title.value,
      content: e.target.content.value
    });
  }
  const delete = async () => {
    await deletePost({ id: id });
  }
  return (
    <form onSubmit={submit}>
      <input name="title" defaultValue={title} />
      <textare name="content" defaultValue={content} />
      <button type="submit">変更</button>
      <button type="button" onClick={delete}>削除</button>
    </form>
  );
}

page.tsx (getPosts)

app/posts/page.tsx
import { Form } from "@/components/form";
import { PostList } from "@/components/post-list";
import { getPosts } from "@/actions/post";
import type { Post } from "@prisma/client";

export default function Home() {
  const posts = await getPosts();
  return (
    <div>
      <h2>新規記事追加</h2>
      <Form />
      <h2>記事一覧</h2>
      <div>
        {posts.map((post: Post) => {
          return (
            <PostList
              key={post.id}
              id={post.id}
              title={post.title}
              content={post.content}
            />
          );
        })}
      </div>
    </div>
  );
}

注意点と感想

今回の記事で紹介したのはserver actionsの基本的な使い方の例ですので、バリデーションなどが一切含まれていない点にご注意ください。

以前はNext.jsでCRUDなどのサーバー処理を行う場合はapiを作ってfetchして...というのが普通だったと思うんですが、server actionsが出てきたおかげでサーバー側の処理をすごく手軽に行えるようになりました。

ちょっとでもserver actionsの便利さが伝われば嬉しいです☺️