中小企業向けに Next.js のフルスクラッチ受託をやっていると、最近は App Router 前提の新規案件がほぼすべてになりました。Server Components と Server Actions を中心に組む構成です。
このとき、地味に悩ましいのが「Server Actions のセキュリティをどこまで真面目にやるか」という線引きです。Next.js 公式のデータセキュリティガイドでも、Server Actions は「常に hostile(敵対的)に扱え」と明記されていて、ページ単位の認証チェックが Server Actions に伝播しない、という前提が強調されています。
大手向けの SaaS だと専属のセキュリティ担当がいて要件を引いてくれますが、中小企業向けのフルスクラッチでは情シスが兼任で1人体制、ということも珍しくありません。IT 人材は 2026年から 2030年にかけて最大 80万人弱不足するという経済産業省の試算もあり、社内にセキュリティのノウハウが薄いまま業務システムが先行して動いている状態は、今後さらに増えそうです。
本記事では、中小企業向けの Next.js フルスクラッチで現実的に運用できる「Server Actions と Data Access Layer の置き方」を、私たちの受託現場の感覚で整理します。
なぜ middleware だけの認証では足りないのか
まず、設計の前提としてはっきりさせておきたいのが、「middleware だけに認証を寄せると危ない」という話です。
2025年に CVE-2025-29927 という重大な脆弱性が公開され、Next.js の middleware ベース認証の限界が広く知られるようになりました。WorkOS の2026年版の認証ガイドでも、middleware を「玄関の警備員」、Data Access Layer を「搭乗ゲートでの本人確認」に例え、両方そろってはじめて防御が成立する、という整理がされています。
中小企業向けの受託でも、これは現実的に効いてきます。たとえばこんなケースです。
- 経営者向けの売上管理画面と、現場スタッフの入力画面が同じ Next.js アプリに同居している
- 役割(role)によって、見せるデータと、書き換えていいデータが違う
- 退職者が出たときに、すぐに権限を絞りたい
middleware だけの認証だと、Server Actions 内のクエリが「ログイン済みなら誰でも実行できる」状態になりがちです。退職者のセッションが残っていた、ロールチェックが特定の画面にしかなかった、といった事故が起こりやすくなります。
このあたりの考え方は、私たちの 【2026年版】Next.js 16 + Firebase Authentication で実現するセキュアな会員制サイト構築 でも触れていますが、本記事ではより業務システム寄りの観点で深掘りします。
Data Access Layer (DAL) を中小企業向けにどう置くか
Next.js 公式ガイドでは、データ取得と認可ロジックを集約する「Data Access Layer(DAL)」のパターンが推奨されています。私たちの受託案件でも、画面数が 20 を超えるあたりから、DAL を切らないと保守がしんどくなる感覚があります。
中小企業向けのフルスクラッチでは、シンプルに次のような置き方にすることが多いです。
src/server/dal/ 配下に、ドメイン単位でファイルを分ける(例: orders.ts, customers.ts, users.ts)
- 各ファイルの先頭で
import "server-only" を必ず付け、クライアントバンドルに混入しない構成にする
- どの関数も「セッション検証 → 認可チェック → クエリ → DTO 変換」の順で書く
- 戻り値は ORM のインスタンスではなく、画面に必要な分だけのプレーンオブジェクト(DTO)を返す
ざっくり書くと、こんなイメージです。
// src/server/dal/orders.ts
import "server-only";
import { verifySession } from "@/server/auth/session";
import { prisma } from "@/server/prisma";
export async function listOrdersForCurrentUser() {
const session = await verifySession();
if (session.role !== "staff" && session.role !== "manager") {
throw new Error("FORBIDDEN");
}
const orders = await prisma.order.findMany({
where: { tenantId: session.tenantId },
select: { id: true, code: true, totalAmount: true, status: true },
});
return orders;
}
ポイントは、「呼び出し側でロールチェックを書かない」という運用にすることです。中小企業向けの受託では、エンジニアが入れ替わっても DAL を見ればドメインごとの認可ルールが分かる、という状態を維持するのが現実的です。
このパターンは Next.js 公式のデータセキュリティガイドでも推奨されていて、特別に複雑なフレームワークを足す必要はありません。
Server Actions 側でやるべき最低ライン
DAL を切ったうえで、Server Actions 側では次のラインを守るようにしています。
- Zod などで入力スキーマを必ず検証する
- DAL の関数を呼ぶ前に、もう一度セッションを検証する
- エラーメッセージに内部情報を載せない
- 戻り値も DTO に揃える(ORM 直返しをしない)

Authgear の2026年版 Next.js セキュリティガイドでも、Server Actions の入力は常に Zod 等で検証することが「最低ライン」として強調されています。CSRF トークンを Next.js 側で自動的に発行する仕組みは存在しないので、入力検証と認可の二段構えで守る、というのが 2026 年時点の現実解です。
たとえば、受注ステータスを更新する Server Action なら、こんな書き方になります。
// src/app/(dashboard)/orders/actions.ts
"use server";
import { z } from "zod";
import { verifySession } from "@/server/auth/session";
import { updateOrderStatus } from "@/server/dal/orders";
const schema = z.object({
orderId: z.string().min(1),
status: z.enum(["preparing", "shipped", "completed"]),
});
export async function updateOrderStatusAction(formData: FormData) {
const session = await verifySession();
const parsed = schema.safeParse({
orderId: formData.get("orderId"),
status: formData.get("status"),
});
if (!parsed.success) {
return { ok: false, message: "入力値が不正です" };
}
try {
await updateOrderStatus({
session,
orderId: parsed.data.orderId,
status: parsed.data.status,
});
return { ok: true };
} catch {
return { ok: false, message: "更新に失敗しました" };
}
}
ありがちなミスとして、catch で受け取った error のメッセージをそのままユーザーに返してしまう、というものがあります。これを続けていると、スタックトレースや内部 ID が UI 経由で漏れることがあります。中小企業向けの業務システムでも、データの取り扱い責任は同じなので、エラーは「ユーザー向け文言」と「ログ向け詳細」を分けて扱うことを徹底しています。
中小企業向けに「やりすぎないライン」をどう引くか
セキュリティの話は、踏み込もうと思えばどこまでも踏み込めます。ただ、中小企業向けの受託で大手 SaaS と同じレベルの設計を要求すると、予算と納期が一気に現実から離れます。
私たちが受託の見積り段階で意識しているのは、「最低限ここまではやる」「やるかどうかは要件次第」のラインを最初に切ることです。
最低限ここまではやる:
- middleware で未ログインを弾く
- DAL でセッション検証と role チェックを集中管理する
- Server Actions で Zod 検証と認可チェック
- ログには個人情報を載せず、外形のみ残す(エラートラッキングと組み合わせる)
要件次第で足すもの:
- 監査ログ(誰が何をいつ更新したか)の専用テーブル
- 多要素認証(MFA)
- IP 制限・地理制限
- 退職者処理の SOP と仕組み化
中小企業向けの場合、監査ログや MFA は「いま必要ですか?」を率直に確認します。実態としては、まずは最低ラインを満たしたうえで、運用しながら順次足していくケースが多いです。AI 駆動開発で実装速度は上がっていますが、「全部やる」ではなく「必要なところから順に」という考え方は変えていません。
このあたりは 【2026年版】中小企業の社内業務を Next.js で作り直す実務ガイド や 【2026年版】Next.js × AIエージェントで業務を自動化する実務ガイド でも触れているので、業務システム側の文脈と合わせて読むとイメージがつきやすいかもしれません。
AI 駆動開発との合わせ技で、セキュリティ実装の取りこぼしを減らす
最後に、AI 駆動開発との掛け算の話を少しだけ。
Cursor や Claude Code を使うと、Server Actions と DAL のひな型を一気に生成できます。便利な反面、「生成されたままで認可チェックが抜けている」「Zod スキーマが付いていない」といった抜け漏れが起きやすいのも事実です。
私たちの社内では、AI に対して次のような前提を毎回渡すようにしています。
- 「Server Actions は必ず Zod でスキーマを定義してください」
- 「DAL 関数の冒頭で
verifySession() と role チェックを入れてください」
- 「戻り値は DTO で、ORM のインスタンスを直接返さないでください」
- 「
error.message をそのままユーザーに返さないでください」
このルールを Claude Code の CLAUDE.md や Cursor の Rules に書いておくと、生成コードの初期品質が一段上がります。AI 駆動開発の使い分けは Cursor と Claude Code をどう使い分けているか|中小企業向け Next.js 受託の現場 にもまとめてあります。
「AI で速く作る」と「セキュリティを現実的に守る」は対立しません。ルールを言語化して AI に渡し、人間が DAL のレビューに集中する。この役割分担が、中小企業向けフルスクラッチでも現実的に回るやり方だと感じています。
まとめ
- Server Actions は「常に敵対的」に扱うのが 2026年時点の前提
- middleware だけの認証では足りない。Data Access Layer に認可ロジックを集約する
- 中小企業向けでは「最低限のライン」と「要件次第で足すもの」を最初に切り分ける
- Zod による入力検証、DTO 返却、エラーの分離は最低限でやる
- AI 駆動開発との合わせ技で、抜け漏れを減らしつつ実装速度を保つ
Next.js フルスクラッチでの業務システム開発を検討中の中小企業の方は、株式会社ゼットリンカーの無料相談フォーム からご相談ください。ご予算と要件をうかがったうえで、現実的な設計と見積りをご提案します。
本記事は Next.js 16.x 時点の情報です
最終更新:2026年5月26日