本文へ移動
verified onnext@16.3.5react@19
最終検証日 2026-09-26
Next.js道場

Server Actions must be async の直し方

実際に出るエラー

Error: Server Actions must be async functions.

Error: Only async functions are allowed to be exported in a "use server" file.

Error: A "use server" file can only export async functions, found object.

use server を付けた関数に async が付いていないときに出るビルドエラーです。

文言は3種類ある

同じ原因でも、どこに書いたか、何を export したかで出る文言が変わります。実機で確かめた3つです。

どれも「use server から出せるのは async 関数だけ」という1つの規則
書いたもの出る文言
関数の中の use server・async なしServer Actions must be async functions.
ファイルから数値や文字列を exportOnly async functions are allowed to be exported…
ファイルからオブジェクトを exportA "use server" file can only export async functions, found object.

検索するときは、自分の手元に出た文言で探してください。1つだけ覚えていると、別の文言に当たったときに別の問題だと思ってしまいます。

1つめ:async が付いていない

export default function Page() {
  function save(formData: FormData) {
    'use server'
    console.log(formData.get('a'))
  }
  return <form action={save}><input name="a" /></form>
}
Error: Server Actions must be async functions.

中で await していなくても async が要ります。呼ぶ側から見ると、この関数は常にネットワークを越えます。結果は必ずあとから返るので、戻り値が Promise であることが形として決まっています。

2つめ:関数以外を export している

'use server'
 
export const LIMIT = 10
 
export async function save(formData: FormData) {
  console.log(formData.get('a'))
}
Error: Only async functions are allowed to be exported in a "use server" file.

save は async なのに落ちます。落としているのは LIMIT のほうです。

なぜ関数だけなのか

use server と書いたファイルの export は、ブラウザから呼べる入口として登録されます。

このファイルの export は、全部が入口の候補になる
use server のファイル
export async function save()
export async function remove()
ブラウザ側
フォームの action
ボタンの中から呼ぶ

越えられる: FormData・文字列・数値

越えられない: 定数(LIMIT)

越えられない: 型定義

越えられない: ふつうの関数

入口になれるのは呼び出せるものだけです。定数や型が混ざると、ファイル全体として条件を満たしません。1つでも関数以外があれば止まります。

直し方

置き場所を3つに分けます。

app/
  actions.ts     'use server'。async 関数だけ
  limits.ts      定数と型。印は付けない
  page.tsx       画面。両方から読む
import { save } from './actions'
import { LIMIT } from './limits'
 
export default function Page() {
  return (
    <form action={save}>
      <input name="a" maxLength={LIMIT} />
    </form>
  )
}

実機で確かめました。この形でビルドは通り、ページは事前に描かれたままです。送信先を持っていること自体は、そのページを動的にしません(キャッシュの考え方)。

関数の中とファイルの先頭は、効く範囲が違う

同じ印でも、掛かる範囲が違う
関数の中に書く
function save() {
  'use server'
  // この関数1つだけ
}

外側の関数には掛からない。定数の制限も無い。

ファイルの先頭に書く
'use server'
// このファイルの export 全部

export したもの全部が対象。関数以外は置けない。

送信先が増えるなら、ファイルの先頭に書く形のほうが整理しやすくなります。そのぶん、定数を一緒に置けないという制限が付いてきます。

出典

このページの内容は上記の一次情報を基準に書いています。個人ブログや二次情報を根拠にしていません。

理解できたか試す

では、このコードはどうなりますか。

フォームの送信先を作りました。中で待つ処理が無いので async を付けていません。ビルドするとどうなりますか。

この問題を解くこのテーマの問題 3 問