はじめに
個人開発で、地図ベースでスポットを投稿・検索でき、独自の評価軸でレビューもできるWebアプリを作っています。実装に入る前、バックエンドの構成を「フロントはNext.js、APIはHonoで分離する」か「Next.js単体で完結させる」かで検討しました。この記事では、その比較と、最終的にNext.js単体構成に決めた理由をまとめます。
個人開発で「フロントとバックエンドを分けるかどうか」「REST APIを持つべきかどうか」で迷っている人の判断材料になれば、と思い書きました。
技術スタックと選定理由
| レイヤー | 技術 | 選んだ理由 |
|---|---|---|
| フレームワーク | Next.js (App Router) | SSR/SEOに強く、フロント〜サーバーを1つのプロジェクトで完結できる |
| 認証 | NextAuth (Auth.js) + Google OAuth | 個人開発でメール認証やパスワード管理まで自前実装するのは割に合わない。Googleログイン1本に絞ることで実装・運用コストを最小化 |
| DB / ORM | PostgreSQL + Prisma | 型安全なクエリビルダーとしての実績、マイグレーション管理のしやすさ |
| 地図 | Google Maps系ライブラリ(公式系のReactラッパー) | 古参ライブラリより新しいReact/Next.jsバージョンへの追従が早く、公式メンテという安心感 |
| バリデーション | Zod | TypeScriptの型とスキーマ定義を1箇所にまとめられる |
| ホスティング | Vercel + サーバーレスPostgres | 個人開発・低トラフィック向けに無料枠が手厚く、GitHub連携で push するだけでデプロイが完結する |
この中で一番検討に時間をかけたのが、次に書く「バックエンドフレームワークを使うかどうか」です。
なぜHonoを使わず、Next.js単体構成にしたか
最初はHonoでAPIを分離する構成を想定していた
実装を始める前、「フロントとAPIは分けるもの」という一般的なイメージから、Hono で GET/POST /api/spots のようなREST APIを立て、投稿フォームはそこに fetch する構成を想定していました。
// 検討していた構成: Client Component から Hono の REST API を叩く
const res = await fetch('/api/spots', {
method: 'POST',
body: JSON.stringify({ ...formValues, userId }),
});
要件を洗い出して気づいたこと
ただ、実装方針を詰める段階で要件を整理したところ、読み取り(一覧・詳細画面)はNext.jsのServer Componentがデータベースを直接読みに行けば十分で、Honoが担うのは書き込み1エンドポイントだけになることに気づきました。フレームワークを1つ丸ごと採用する割に、担う役割が驚くほど小さいと分かったのです。
ここで次の2点を自問しました。
- このアプリを叩く外部クライアント(モバイルアプリ、他社サービスからの連携など)を将来持つ予定はあるか?
- 無いなら、公開REST APIを持つ理由は何か?
答えは「1: 無い」「2: 特に理由がない」でした。さらに、Next.js公式ドキュメントでも「Server ComponentからNext.js自身のRoute Handlerにfetchするのはアンチパターン(HTTPが無駄に一往復増える、ビルド時の最適化を妨げる)」と明言されており、Next.jsの中に内部用のREST APIを置くこと自体が推奨されていないことも後押しになりました。
Next.js単体 + Server Actionsに決定
比較の結果、Honoは採用せず、書き込みもNext.jsのServer Actionsで実装する方針に決めました。
// 採用した構成: Server Action を直接呼ぶ
'use server';
export async function createSpotAction(
_prevState: FormState,
formData: FormData,
) {
const session = await auth();
if (!session?.user?.id) return { error: 'ログインが必要です。' };
const parsed = createSchema.safeParse({ /* formDataから組み立て */ });
if (!parsed.success) return { error: '入力内容を確認してください。' };
await create(parsed.data, session.user.id); // userIdはセッションから取得
redirect('/spots');
}
Hono案では「userId をリクエストのbodyで受け取り、なりすましを防ぐバリデーションを別途実装する」という課題を想定していましたが、Server Actionsなら auth() でサーバー側のセッションから直接 userId を取得できるため、この懸念は設計の時点で解消されました。
良かった点
- 型がフロント〜サーバーで地続きになり、DTOのシリアライズ/デシリアライズや型定義の二重管理が要らない
- HTTPの往復が1つ減り、コード量・レイヤーの数がシンプルになる
- 依存ライブラリを1つ増やさずに済むことで、個人開発における一番のコスト=メンテナンスし続ける気力を節約できる
逆にこの判断をすべきでないケース
これは「外部クライアントを持たない」という要件があったからこそ選べた構成です。モバイルアプリを別途作る予定がある、他社に公開APIとして提供したい、といったケースでは判断が逆になります。「みんなHonoやRESTをやめてServer Actionsにすべき」という話ではなく、「フロントとバックエンドを分けるのが当たり前」という前提だけで技術を選ぶ前に、誰がこのアプリを叩くのかを一度洗い出すと、不要なレイヤーを持たずに済む、というのがこの比較で得た教訓です。
まとめ
- 実装前に「本当にそのレイヤーが必要か」を要件から逆算すると、依存とメンテコストを減らせる
- Next.js + Server Actionsの構成は、外部クライアントを持たないWebアプリであれば十分実用に耐える
- 技術選定は流行りではなく、「誰がこのアプリを叩くのか」という要件から逆算するのが結局一番シンプルになる