0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

個人開発でHonoを使うか検討して、結局Next.js単体構成にした話

0
Last updated at Posted at 2026-07-15

はじめに

個人開発で、地図ベースでスポットを投稿・検索でき、独自の評価軸でレビューもできる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は分けるもの」という一般的なイメージから、HonoGET/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点を自問しました。

  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アプリであれば十分実用に耐える
  • 技術選定は流行りではなく、「誰がこのアプリを叩くのか」という要件から逆算するのが結局一番シンプルになる
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?