12
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Next.js 14 × Cursor】初心者がparams非同期化とPrisma generateでハマった話と解決法

12
Last updated at Posted at 2026-01-08

💡この記事で分かること

  • Next.js 14 App Routerの動的ルーティングで params が取得できない問題の解決法
  • Prismaのスキーマ変更が反映されない「無限ループ」からの脱出方法
  • 開発初心者がAI開発ツール(Cursor)を使いこなすために私自身が考えたこと

対象読者: Next.js 14 × Prismaで開発中の方、CursorなどのAI開発ツールを使っている方、「コードは合ってるはずなのにエラーが出る...」と悩んでいる方


はじめに

株式会社Dirbatoの社内技術支援横断組織「Backbeat」で、最新技術の調査や社内ツールの保守開発を担当している山田です!

私はCursorで共同旅行計画アプリ「PonChord」を、技術キャッチアップを兼ねて個人開発しています。
今回は、そのアプリの開発中に特に頭を抱えた バックエンド周りの「2大ハマりポイント」 について、初心者ならではのAI開発での苦悩とその解決方法を書きたいと思います。

ハマったポイントは以下の2つ:

  1. Next.js 14 App Routerで params が取得できずにエラーになる問題
  2. Prismaのスキーマを変更しても、なぜか反映されずにエラーになる問題

これらはエラーの原因をなかなか見つけることができず、上手くCursorに改善点を伝えられずに、開発を進める中でかなり悩んだところでした。

どちらも「コードが間違っている」というよりは、「ツールのルール(作法)」の問題なので、初心者には原因が特定しにくく、非常にハマりやすいポイントだと思います。同じ構成で開発している方の助けになれば幸いです!

▼「PonChord」については前回記事で紹介しています

前提知識:Next.js 14とPrismaって何?

まず、今回のエラーに関わる技術について、私が理解したことを書きます。(勉強中なので、間違いがあればご指摘ください...!)

① Next.js 14 (App Router)

Reactという人気のライブラリをベースにした、Webサイトを作るためのフレームワークです。
「バージョン14」の「App Router」というのは、ファイルの置き方に関する新しいルールのことです。

何ができる? フォルダを作ると、それがそのままURLになります。

  • 例: app/about/page.tsx というファイルを作ると www.example.com/about というページができる。

動的ルーティング(Dynamic Routing) これが今回のキーポイントです!

  • フォルダ名を [id] のように [] で囲むと、そこは 「何が入ってもいい場所」 になります。
  • 例: app/api/resource/[id]/route.ts と作ると、/api/resource/123/api/resource/abc など、どんなIDでも受け取れるようになります。この 123abc という値をプログラムの中で受け取る変数が params です。

② Prisma

プログラム(Next.js)とデータベース(PostgreSQLなど)の仲介役(ORM) です。

役割: 本来、データベースを操作するには「SQL」という難しい言語が必要ですが、Prismaを使うとJavaScript/TypeScriptの書き方でデータベースを操作できます。何それ美味しいの?状態から使い始めましたが、めちゃくちゃ便利でした。

schema.prisma: 「設計図」ファイルです。「ユーザーには名前とメールアドレスがある」といった定義をここに書きます。

型定義(Type Safety): Prismaの最大の特徴です。設計図を元に、「ユーザーデータには必ず名前が入っているはずだ」というルール(型)を自動で作ってくれます。これにより、コードを書いている最中に「あ、名前の綴りが間違ってるよ」と教えてくれるようになります。これがあるおかげで、タイポによるバグが激減しました!

1. App Routerの洗礼:params は Promise である

Next.js 14のApp Routerで動的ルーティングを使用したAPI(例: /api/resource/[id]/route.ts)で、IDを取得しようとすると不可解な挙動に遭遇しました。

❌ エラーの状況

params.idundefined になったり、404/500エラーが発生したりしました。

発生したエラーログ(例):

GET app/api/resource/[id]/route.ts 500 (Internal Server Error)
TypeError: Cannot read properties of undefined (reading 'id')

修正前のコード:

route.ts
export async function GET(
  request: Request,
  { params }: { params: { id: string } }
) {
  const journeyId = params.id // ⚠️ ここが undefined になることがある
  // ...処理
}

「え、なんで?ちゃんと [id] って書いてるのに!」と頭を抱えていました。Cursorに「なぜこのエラーが出るのか」と聞いても、コードはあってるよ、と的確な答えが返ってこず、公式ドキュメントを見ても知識不足で次々と疑問が湧き、エラーログを行ったり来たり……。完全に迷子でした。

そんな中、ChatGPTに「わかりやすく教えて、、!!」と泣きついたところ、(私的には)分かりやすい例えをしてくれました。この例えのお陰でこのエラーの根本的な原因を理解することが出来ました!

🍽️ わかりやすい例え:レストランの注文

  • 昔(Next.js 13以前など): 店員さんに「注文いいですか?」と言うと、その場ですぐに注文を聞いてくれました。

  • (コード:params.id と書けばすぐにIDが取れた)

  • 今(Next.js 14の一部): 店員さんに声をかけると、「後で注文を聞きに行くから待っててね」という整理券(Promise)を渡されるようになりました。

  • それなのに、私は整理券そのものを料理だと思って食べようとして、「これ料理じゃないよ!(エラー)」 となっていたのです。

なるほど!!! この例えで、ようやく腑に落ちました。
わたし、Next.js 14 つかってるぢゃん!!!!

✅ 解決策:動的ルーティングの引数の型はPromise型で、値はPromise.resolve で解決する

Next.js 14以降は、動的ルートの paramsPromise として扱われると判明。
どうやらCursorくんは、引き数の型をそもそもPromise型ではない書き方で書いてしまい、コード上エラーが無いように見え、詰まってしまっていた模様でした。
そのため、普通に通されるのではなく、「まず整理券(Promise)をもらう」必要がある。そして「整理券(Promise)」をもらったら、「呼び出されるまで待つ(await)」という処理が必要でした。

修正後のコード:

route.ts
export async function GET(
  request: Request,
  { params }: { params: Promise<{ id: string }> | { id: string } } 
) {
  try {
    const resolvedParams = await Promise.resolve(params)
    const journeyId = resolvedParams.id
    
    if (!journeyId) {
      return NextResponse.json({ error: 'IDが必要です' }, { status: 400 })
    }
    // ...以降、journeyId を使用してDB検索などを行う
  } catch (error) {
    // エラーハンドリング
  }
}

この対応は、[id] だけでなく、[itemId][token] など、すべての動的APIルートに適用する必要がありました。

原因を理解してからは、Cursorの.cursor/rules配下のファイルで「すべての動的ルートで params を Promise として扱い、await Promise.resolve(params) で解決するパターンを適用して」と具体的に指示しておくことで、同じような動的ルーティングを使用したAPIの実装を爆速で完了できました!やったー!

ポイント: Promise.resolve() は、引数がPromiseならそのまま解決し、Promiseでなければそのまま返してくれる便利な関数です。防御的コーディングとして非常に有効!

2. Prismaスキーマ変更の「反映されない」無限ループ

❌ エラーの状況:スキーマを変えたのに...

データベースにPostgreSQL、ORMにPrismaを使用していますが、「行きたいところリスト」にタグ機能を実装するため、tag フィールドを追加した時です。

schema.prisma を書き換えて保存したのに、いざAPIを叩くと以下のようなエラーが出ました。

エラーログ:

// API実行時
Unknown field `tag` for include statement on model WishlistItem.

または

// TypeScriptのコンパイルエラー
Property 'tag' does not exist on type 'WishlistItem'.

「え、ちゃんとスキーマに追加したのに!?」と何度もスキーマファイルを見直しましたが、問題はそこではありませんでした……。Cursorに「スキーマ変更したのにエラーが出るたすけて」と泣きつきました。。

このエラーも私の知識不足によりCursorに上手く修正してほしい点を伝えられず、修正に時間がかかってしまいました。結局このエラーに関してはCursorに壁打ちしながら修正することが出来ました。(Cursorとの対話を繰り返すうちに、ようやく原因に辿り着けた感じです)

💭 ここで痛感したこと

Cursorは非常に優秀なツールですが、やはりしっかりとエラーの原因を理解したうえで改善するためのプロンプトを書かないと、時間がかかってしまう場合があると私は思います。

「スキーマを変更したのにエラーが出る」という漠然とした相談では、Cursorも「コードのどこが間違っているのか」を探す方向に進んでしまい、本質的な 「手順の抜け漏れ」 には気づきにくいんですね。

この優秀なCursorを使いこなすためには、もっともっと私自身が成長する必要があるなと強く思いました。 AIは強力なパートナーですが、「何が問題なのか」を理解し、「どう解決したいのか」を言語化できるのは、結局のところ人間である私たち自身なんですよね。

✅ 解決策:変更時の「鉄の掟」ルーティン

Prismaの場合、schema.prisma ファイルを書き換えるだけでは変更は反映されません。型定義ファイル(Prisma Client)の再生成が必須です。

Prismaでは、変更を適用するために以下の手順が必須です。

手順1. スキーマの変更

schema.prisma
model WishlistItem {
  id    String @id @default(cuid())
  // ...
  tag   String? // 追加!
}

手順2. DBへの反映

npx prisma db push --accept-data-loss

手順3. クライアントの再生成 ← 今回ここが抜けていた!

ここを忘れがちですが、これをやらないとTypeScriptが新しいフィールド tag を認識しません。

npx prisma generate

特に3番目の generate は、「TypeScriptの型定義」を作り直す作業です。これをやらないと、エディタやNext.jsは新しい項目の存在を知ることができません。ここが盲点でした...!

手順4. 開発サーバーの再起動

# 一度 Ctrl+C で止めてから
npm run dev

この手順を理解してからは、Cursorに「Prismaスキーマを変更したから、db pushgenerate を実行して」と具体的に指示することで、ターミナルコマンドまで提案してもらえるようになりました! やはり、「何をすべきか」を理解していることが、Cursorへの的確な指示につながるんですね。

Tips: この4ステップを「push → generate → restart」のリズムで覚えると、もう迷いません!Cursorの.cursor/rules配下のファイルに「Prisma変更時の手順」として書いておくと、次回からこの実装手順を反映することが出来て便利です。

まとめ:ハマりポイントを乗り越えて

今回の開発で得た教訓は以下の3点です。

  1. Next.js App Routerの params は非同期前提で扱う

    • Next.js 14の動的URLを使うときは、「データは後から来る(非同期)」と思って待ち受けるコードを書くこと。
  2. Prisma変更時は push → generate → restart
    このリズムを崩すと、謎のエラーで時間を溶かすことになります。(実体験)

    • データベースの定義を変えたら、「DBの更新」と「プログラム側の更新(generate)」の両方を必ずセットで行うこと。
  3. Cursorを使いこなすには「理解」が必要

    • エラーの原因を理解し、的確なプロンプトを書けるかどうかが開発速度を大きく左右する

どちらも「コードが間違っている」というよりは、「ツールのルール(作法)」の問題なので、初心者には原因が特定しにくく、非常にハマりやすいポイントです。
だからこそ、こうした「ハマりポイント」を言語化して共有することが大切だと感じています。

同じところで躓いている方の助けになれば嬉しいです! もし「私もここでハマった!」という経験があれば、ぜひコメントで教えてください。一緒に乗り越えましょう!

12
1
2

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
12
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?