🐸

Next.js 16: "use server" / "use client" 境界で「export 起因の事故」を潰す

に公開

Next.js 16: "use server" / "use client" 境界で「export 起因の事故」を潰す(公式エラーメッセージに沿った最小ルール)

この記事は、Next.js 16 の公式ドキュメントと公式リポジトリのエラーメッセージ(invalid-use-server-value など)を根拠に、

  • "use server" と "use client" の 境界を跨ぐ import/export で
  • 「export 設計が原因」のリンターエラー/ビルド時エラー/実行時エラーを起こしにくい構造

を 第三者が再現できる形で整理します。


0. 結論(質問への回答と、NO になる但し書き)

質問

この記事に書かれた事柄さえ守れば
"use server" "use client" 間での export が原因 の リンターエラーや実行時エラーは起きない、という理解でよいか?

答え

NO

NO になる但し書き(抜け漏れなく)

  1. Next.js 本体の不具合・挙動差(公式リポジトリ上の issue / discussion による報告)により、設計ルールを守っていてもエラーが出るケースが「ゼロ」とは断言できません。
  2. Server Actions には、export 設計とは別の理由で、実行時に Failed to find Server Action ... のようなエラーが起き得ます(公式ドキュメントに「なぜ起きるか/どう直すか」が説明されています)。
  3. Next.js 16 では next lint 自体が削除され、ESLint の “唯一の最強設定” を Next.js が公式に 1つに定義していません。そのため「世界で唯一の最強 lint 設定で絶対に落ちない」までを公式根拠だけで保証できません。

ただし、この記事のルールを守ることで 「export 設計が原因で起きる代表的な事故」(後述)は、構造的に潰せます。


1. まず押さえる公式仕様(ここがブレると事故る)

1.1 "use client" は「クライアント境界」を作る

'use client' は、そのファイルを **クライアントで動くコンポーネントの入口(境界)**として宣言します。
境界ファイルから export されたコンポーネントが、クライアント側のエントリーポイントになります。

1.2 "use server" は「サーバーで実行される関数/ファイル」を指定する

use server は、関数(またはファイル)を サーバー側で実行させるためのディレクティブです。
ファイル先頭に置くと「そのファイル内の関数はサーバーで実行される」と説明されています。


2. 重要:"use server" の export 制約は「import した時」だけの話ではない

よく混乱するポイントなので、先にハッキリ書きます。

  • Next.js の公式エラードキュメント(invalid-use-server-value)は、
    「"use server" ファイルが async 関数以外を export したときに発生する」 と説明しています。
  • つまり本質は **「"use server" を付けたファイルの export 形」**です。
  • エラーが “いつ見えるか” は、開発サーバーやビルドが そのモジュールを処理(評価)したタイミングです
    (実務上は「どこかから import されてモジュールグラフに入ったとき」に顕在化しやすい)。

2.1 典型エラー:"use server" file can only export async functions ...

「"use server" ファイルで async 以外(定数、object、enum、class など)を export してしまう」事故です。
公式ドキュメントの通り、修正方針は async 関数だけを export する です。


3. ルール(これだけ守れば “export 起因” の代表事故を再現しにくい)

ポイントは **「"use server" を置くファイルを“async 関数だけ export する専用ファイル”として固定」**することです。

3.1 ルールA:"use server" ファイルは async 関数だけ export

✅ OK

// src/actions/user.ts
'use server'

export async function updateUser(formData: FormData) {
  // ...
}

export const deleteUser = async (id: string) => {
  // ...
}

❌ NG(この記事では禁止)

'use server'

export const X = 1
export default { a: 1 }
export function syncFn() {}
export class C {}
export enum E { A }

3.2 ルールB:"use server" ファイルで barrel export / re-export を禁止

✅ OK:async 関数を このファイルで定義して export
❌ NG(混入が起きやすい)

'use server'
export * from './anything'
export { something } from './other'

3.3 ルールC:共有したい “値/enum/class/sync関数” は必ず別ファイルへ

✅ OK(分離)

// src/shared/enums.ts
export enum LogLevels { Info = 'INFO', Warn = 'WARN', Error = 'ERROR' }
// src/actions/log.ts
'use server'
import { LogLevels } from '../shared/enums'

export async function sendLog(level: LogLevels, message: string) {
  // ...
}

3.4 ルールD:"use client" 側は “import して使うだけ” に寄せ、中継 re-export を増やさない

✅ OK

'use client'
import { updateUser } from '../actions/user'

export function Form() {
  return <button onClick={() => updateUser(new FormData())}>update</button>
}

❌ NG(この記事では避ける:境界追跡が難しくなる)

'use client'
export { updateUser } from '../actions/user'

4. Next.js 16 で「厳しめチェック」を固定する(再現手順)

4.1 重要:Next.js 16 では next lint が削除されている

Next.js 16 では next lint が削除され、ESLint CLI を使う形に移行しています。
したがって、この記事では ESLint CLI を前提にします。

4.2 Next.js 公式の“厳しめプリセット”として eslint-config-next/core-web-vitals を使う

公式ドキュメント上、eslint-config-next/core-web-vitals は「ベース設定に加え、Core Web Vitals に影響するルールを warning → error に上げる」構成で、多くのプロジェクトに推奨されています。

例:CI で「警告ゼロ」を強制し、型チェックとビルドもセットで通す

package.json

{
  "scripts": {
    "lint": "eslint . --max-warnings=0",
    "typecheck": "tsc -p tsconfig.json --noEmit",
    "build": "next build",
    "ci": "npm run lint && npm run typecheck && npm run build"
  }
}

eslint.config.mjs(Flat config 例)

import nextVitals from 'eslint-config-next/core-web-vitals'
import nextTs from 'eslint-config-next/typescript'

export default [
  ...nextVitals,
  ...nextTs,
]

注意:ESLint はルール追加でいくらでも厳しくできます。
この記事は「Next.js 公式が提供する設定の範囲で、再現しやすい“厳しめ”」を提示しています。


5. この構造で潰せる “export 起因” の事故(代表例)

  • "use server" ファイルで async 以外(enum / const / object / class など)を export してしまう
    → ルールA・Cで回避
  • "use server" ファイルで export * / re-export を使い、意図せず async 以外が混入
    → ルールBで回避
  • "use client" 側で中継 re-export を増やして境界が読めなくなる
    → ルールDで回避

6. それでも残る “export 設計だけでは 0 にできない” 実行時エラー

代表例として、公式ドキュメントに載っている Failed to find Server Action ... は、
export の形が正しくても デプロイや実行環境に起因して発生し得ます。

この種のエラーは「export 設計ルール」では 100% 予防できません。


7. 参考リンク(公式)

Discussion