この記事の要点
'use server'を付けた非同期関数はサーバーで実行される関数になり、クライアントから直接呼べる。API を自分で作らなくてよい。formのactionに渡すと、FormDataが自動的に引数として届く。- UI を経由せず直接 POST できる。認証と認可の確認は必ず関数の中で行う。
- 送信中の状態は
useActionStateが返すpendingで表現できる。 - 更新後は
updateTagやrevalidatePathでキャッシュを整え、必要ならredirectで遷移する。
従来、ブラウザからデータを更新するには API エンドポイントを作り、fetch で呼び、レスポンスを見て画面を更新する、という手順が必要でした。Next.js の Server Functions はこの往復をフレームワーク側が引き受けます。関数を書いてフォームに渡すだけで、通信も画面更新もつながります。フォーム送信の文脈で使うものを特に Server Actions と呼びます。
1定義する
非同期関数の本体先頭、またはファイルの先頭に 'use server' と書きます。ファイル先頭に書いた場合は、そのファイルのすべてのエクスポートが対象になります。
// app/lib/actions.ts
'use server'
import { auth } from '@/lib/auth'
export async function createPost(formData: FormData) {
const session = await auth()
if (!session?.user) {
throw new Error('Unauthorized')
}
const title = formData.get('title')
const content = formData.get('content')
// データを保存する
// キャッシュを整える
}
サーバーコンポーネントの中に直接書くこともできます。小さな処理ならこちらが手軽です。
export default function Page() {
async function createPost(formData: FormData) {
'use server'
// ...
}
return <form action={createPost}>{/* ... */}</form>
}
クライアントコンポーネントの中で定義することはできません。別ファイルに 'use server' を付けて置き、それを import して使います。
2フォームから呼ぶ
もっとも基本的な呼び出し方が、form の action に関数そのものを渡す形です。
import { createPost } from '@/app/lib/actions'
export function Form() {
return (
<form action={createPost}>
<input type="text" name="title" />
<textarea name="content" />
<button type="submit">作成</button>
</form>
)
}
送信すると、name 属性を持つ入力値が FormData にまとめられて関数の引数に渡ります。onSubmit も preventDefault も fetch も書きません。
サーバーコンポーネント上のフォームであれば、JavaScript がまだ読み込まれていない状態でも送信が成立します(プログレッシブエンハンスメント)。クライアントコンポーネント上のフォームの場合は、送信が一時的に待ち行列に入り、ハイドレーション完了後に処理されます。
3イベントハンドラから呼ぶ
フォーム以外からも呼べます。クライアントコンポーネントで import して、通常の非同期関数のように扱います。
'use client'
import { incrementLike } from './actions'
import { useState } from 'react'
export default function LikeButton({ initialLikes }: { initialLikes: number }) {
const [likes, setLikes] = useState(initialLikes)
return (
<>
<p>いいね: {likes}</p>
<button
onClick={async () => {
const updated = await incrementLike()
setLikes(updated)
}}
>
いいね
</button>
</>
)
}
ただし、クライアントからの呼び出しは 1 つずつ順番に処理されます。並列にデータを取りたい場合は、サーバーコンポーネント側で取得するか、1 つの関数の中でまとめて処理してください。
4送信中の状態を出す
useActionState は、状態・アクション・pending フラグの 3 つを返します。二重送信の防止にも使えます。
'use client'
import { useActionState } from 'react'
import { createPost } from '@/app/actions'
const initialState = { message: '' }
export function Form() {
const [state, formAction, pending] = useActionState(createPost, initialState)
return (
<form action={formAction}>
<input type="text" name="title" required />
<textarea name="content" required />
{state?.message && <p aria-live="polite">{state.message}</p>}
<button disabled={pending}>作成</button>
</form>
)
}
この形にする場合、サーバー側の関数は第 1 引数に前回の状態を受け取る形になります。
'use server'
export async function createPost(prevState: any, formData: FormData) {
const title = formData.get('title')
// 検証に失敗したら「投げる」のではなく「返す」
if (!title) {
return { message: 'タイトルを入力してください' }
}
// ...
}
入力ミスのような想定内の失敗は例外にしないのが原則です。戻り値として表現し、画面にメッセージを出します。例外を投げるとエラー境界に落ち、フォームの入力内容ごと画面が差し替わってしまいます。この考え方は エラーハンドリングとエラー境界 で詳しく扱います。
5更新後の後始末
データを書き換えたら、表示を新しい状態に合わせる必要があります。用途に応じて使い分けます。
| やりたいこと | 使う API |
|---|---|
| 自分の変更を即座に画面へ反映 | updateTag('user-1') |
| 他の閲覧者にも少し遅れて反映 | revalidateTag('posts', 'max') |
| 特定のパスをまとめて作り直す | revalidatePath('/posts') |
| 表示だけを取り直す | refresh() |
| 完了後に別ページへ移動 | redirect('/posts') |
'use server'
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'
export async function createPost(formData: FormData) {
// 保存処理
revalidatePath('/posts')
redirect('/posts')
}
redirect は制御用の例外を投げる実装になっているため、その後ろのコードは実行されません。キャッシュの無効化は必ず redirect より前に書いてください。
6Cookie を操作する
Server Function の中では cookies() で取得・設定・削除ができます。設定や削除を行うと、現在のページとレイアウトがサーバー側で再描画され、新しい値が反映された状態になります。
'use server'
import { cookies } from 'next/headers'
export async function setTheme(value: string) {
const cookieStore = await cookies()
cookieStore.set('theme', value)
}
7つまずきやすいところ
Server Function は POST で直接叩けます。「管理者にしかボタンを見せていない」は防御になりません。すべての関数の冒頭でログイン状態と、その資源に対する権限を確認してください。
①認可 → ②入力検証 → ③更新 → ④キャッシュ無効化 → ⑤(必要なら)遷移、の順で書くと抜けが出にくくなります。
- クライアントコンポーネントの中で
'use server'を書いてしまう:定義はできません。別ファイルに切り出します。 - props で渡すときの命名:クライアントコンポーネントへ関数を渡す場合、
updateItemActionのように Action で終わる名前にしておくと、リンターの支援を受けやすくなります。 - 戻り値が渡せない:クライアントへ返る値は React がシリアライズできるものに限られます。クラスインスタンスなどはそのまま返せません。
- 複数台構成で「Server Action が見つからない」:インスタンスごとに暗号鍵が変わるためです。共通の鍵を環境変数で指定します(本番ビルドとセルフホスト)。
更新処理が書けるようになったら、失敗したときの振る舞いを設計します。
子ページはありません
人気ページ
- 1 Eclipseで「サーバーに追加または除去できるリソースがありません。」の原因と対処法
- 2 tomcat の起動 / 停止ログと catalina.log・catalina.out の違い
- 3 JavaScript で base URL を取得する方法|window.location.origin
- 4 YouTube Data API v3 エラー一覧|403・400・404 の原因と対処
- 5 Laravel エラー一覧|500/Blade/DB 接続/ルーティングの代表エラー
- 6 3Dグラフィックスとは|モデリング/レンダリング/主要ソフトウェア (Blender / Maya)
- 7 Spring Frameworkのアノテーション一覧
- 8 【Spring】@Valueアノテーションとは
- 9 CATALINA_HOME の確認方法 (Linux / Mac)
- 10 【Spring】@Autowiredアノテーションとは
最近更新/作成されたページ
- プロジェクトをTomcatプロジェクトとして認識させる方法 2026-10-07 22:32:50
- MySQLの1366 Incorrect string value|Laravelの文字コード・絵文字エラー 2026-10-07 21:54:03
- curlの証明書ホスト名不一致|旧エラー51・現行60の確認と対処 2026-10-07 21:54:03
- LaravelのMassAssignmentException|fillableの原因と安全な対処 2026-10-07 21:54:03
- Eclipse で Tomcat の起動ログがコンソールに出ない時の確認手順 2026-10-07 21:54:02
- MySQLにおける中央値(Median)の導き方(バージョン8未満) 2026-10-07 13:49:45
- getInputForward 2026-10-07 13:41:15
- JSONから配列に変換 2026-10-07 13:41:15
- ビューから値をモデルに格納しコントローラーで受け取る方法 2026-10-07 13:23:41
- Laravelのテーブル作成と定義変更|マイグレーション・up/down・注意点 2026-10-07 13:23:41
- NumPy 配列に要素を追加する方法 (append / concatenate) 2026-10-07 13:23:41
- MariaDB・MySQLで現在日時を取得する方法|NOW・タイムゾーン・保存型 2026-10-07 13:13:36
- 【django】テンプレートで定数を使用する方法 2026-10-07 13:10:15
- Spring BootにおけるApplication.propertiesの環境依存設定の分割方法 2026-10-07 12:09:35
- Not supported for DML operations【Springエラー】 2026-10-07 11:09:38