👏

Next.jsで練習するページ遷移型の検索機能

に公開

この記事ではNext.jsを使いながら、Webの基本にのっとった「ページ遷移型の検索機能」というわかりやすい題材を学びます。「遷移」と書くとなんだか難しく感じますが、その仕組みはとてもシンプルです。

また、コーディングの途中でAIツールも使いますので、AIを使ってNext.jsやReactのコードを書き、読んで理解する練習にもなります。

本記事の学習内容: ページ遷移型の検索

なぜページ遷移型の検索を題材にするのか?

ひとことで言うと、簡単なのに十分学ぶ価値があるからです。

Next.jsのようなフレームワークを使っていると、いくらでも複雑で凝った設計・実装が出来てしまいます。検索機能ひとつとっても色々な設計・実装が考えられますが、一度にいろんなNext.jsやReactの機能を盛り込んでしまうと「いま自分が何を学んでいるのか?」がわかりづらく、学習を困難にしてしまいます。

今回題材にする「ページ遷移型の検索」はNext.jsの重要な機能であるルーティングとデータ取得に的を絞って学ぶのに最適で、同時にWebの基礎となる仕組みを学ぶのにも適しています。

この記事で学ぶこと

この記事では

  • まずNext.jsとReactの機能だけを使って、検索ページのモックアップを作ります。
  • 次に、簡単にデータ取得の方法を学ぶために、ファイルからデータを取得します。
  • 最後に本格的にデータベースと組み合わせるため、Prismaを導入します。

という流れで学んでいきます。

この記事で学ぶこと3ステップ

🔍 検索「結果」のUIパターン

この記事では検索機能のユーザーインターフェース(UI)を以下の2つに分類します。

https://youtu.be/0yDYYzBBhA0

ページ遷移型の検索

ユーザーが検索ボタンをクリックするか、検索条件を変更すると、新しいURLに移動し、ページ全体が再読み込みされます。結果は新しいページで表示されます。

動画で例として紹介しているのはZenn(https://zenn.dev/search) です。

ページ遷移なしの検索

検索操作を行ってもURLは変わらず、ページ全体は再読み込みされません。JavaScriptによって、ページの一部(検索結果リストなど)だけが動的に書き換えられます。いわゆる「SPA(Single Page Application)」的な動作です。

動画で例として紹介しているのはPrisma公式ドキュメント (https://www.prisma.io/docs/orm) です。

ページ遷移型の検索の特徴

私たちが今回題材とするページ遷移型検索機能には、Webアプリケーションを構築する上で非常に重要な特徴があります。

最も大きな特徴は 「URLに検索条件を埋め込む」 点です。

URLによる情報のシェアのしやすさ

ページ遷移型の検索では、検索キーワードや絞り込み条件がURLのクエリパラメータとして埋め込まれます。例えばZennの記事検索で「Next.js」というキーワードを入力すると以下のページに遷移します。

https://zenn.dev/search?q=Next.js

このURL全体をコピーして別の人に送るだけで、相手も全く同じ検索結果ページをすぐに開くことができます。

📜「ページ遷移なしの検索」をシェアする場合

ページ遷移なしの検索でもURLで検索結果を他人と共有することは可能です。ただし、追加の作り込みが必要になります。

これはページ遷移なしの検索、つまりSPA型の動作では検索条件がブラウザのURLに自動的には反映されないためです。Next.jsで作る場合、検索条件はReactの内部状態(useStateや状態管理ライブラリなど)として保持されます。

その上でURLを使って検索結果を共有するためには、以下の追加の作り込みが必要になります。

  • 検索条件のURLへのシリアライズ: Reactの内部状態として保持している検索条件をURLのクエリパラメータなどに変換。これには主に以下2つのパターンがあります。
    • パターン1: ユーザーが検索条件を変更するたびにURLを更新。Next.jsの場合はuseRouter、一般のJavaScriptアプリケーションではhistory.pushStateを使う。
    • パターン2: 検索条件の変更が頻繁すぎる場合、例えば自由入力のテキスト欄がある、多数の検索条件を次々と変更するなど、そのような場合は「(検索結果を)共有」ボタンを押した時にURLをクリップボードにコピー
  • URLからの検索条件のデシリアライズ: 共有されたURLにアクセスがあった際、アプリケーションはURLから検索条件を読み取り、JavaScriptの内部状態に復元。これにより、URLに合致した検索結果を初期表示できます。

これらの処理は、ページ遷移型の検索ではブラウザやWebフレームワークのルーティングが自然に担ってくれる部分ですが、SPA的なアプローチでは開発者が明示的に実装する必要があります。そのため、実装の複雑さが増してしまいます。

典型的な使い方: 検索条件を段階的に絞り込む

URLを使った共有性の高さは、Webサービスで非常によく利用される「だんだんと検索条件を調整していき、検索結果を洗練させていく」という使い方に繋がります。典型的な例としてはグルメサイトの検索機能が挙げられます。

  • まず「東京都」で検索
  • 次に「ラーメン」で絞り込み
  • さらに「1000円以内」で絞り込み

最終的に見つけた、最も洗練された検索結果のURLを友人にシェアできます。

このように、ユーザーが検索を行うたびに新しいURLへ遷移し、そのたびに検索条件をURLに記録していくのが、この機能の基本的な振る舞いです。

コラム:📜そもそもWebはURL一発でシェアできることが画期的だった

現代では当たり前になった「気に入ったコンテンツや検索結果をURLでシェアする」という行為ですが、そもそもWorld Wide Web (WWW) が画期的だったことの一つに、この「URL(URI)がコンテンツを一意に特定する」という仕組みがあります。

Webページは、特定のURLを叩けば、誰でも、いつでも、同じ情報にアクセスできるという設計思想の上に成り立っています。

ページ遷移型の検索機能は、まさにこのWebの根幹である「ステート(状態)をURLに持たせる」という基本に沿ったパターンであり、Next.jsでWebアプリケーションの基本を学ぶ上で、非常に意義のあるテーマと言えるでしょう。

コーディング: 検索入力ページと結果ページの作成

いよいよコーディングを始めます。

AIコーディングツールを使っていきますので、お好きなものを選んでください。私はこの記事を執筆する際にGemini CLIを使いましたが、Claude CodeでもCodex CLIでも構いません。CursorやWindsurfなどのIDE統合型ツールでも大丈夫です。

ちなみに私がGemini CLIを選んだのは無料利用枠が十分確保されているからです。

Next.js初期セットアップ

Next.js公式のQuick start にしたがってcreate-next-appを実行します。(注意: create-next-appを、非推奨になったcreate-react-appと混同しないように)

ここはAIに聞くまでもないので、以下のコマンドを順番に実行します。

ターミナル
npx create-next-app@latest article-nextjs-static-search --yes
cd article-nextjs-static-search
npm run dev

これで、以下のようなページが表示されるはずです。

Next.jsの初期ページ

以下のプロンプトで不要なコードをクリアしましょう。ここからAIを使っていきます。

プロンプト
JSXとCSSをすべてクリアして、画面にhello worldとだけ表示してください。

Gemini CLIでのプロンプト入力

結果: git commit

これで、page.tsx, page.module.css, global.cssといったファイルの中身をほぼまっさらな状態にしてコーディングをスタートできます。

page.tsx
export default function Home() {
  return (
    <h1>hello world</h1>
  );
}
// `page.module.css`, `global.css`は完全な空ファイル

検索入力ページを作る

次のプロンプトで検索入力ページを作ります。

プロンプト
@app/page.tsxに検索用の<input>要素とボタンを配置してください。
これらの要素は画面中央にセンタリングされます。
検索の<input>と<button>は横並びにしてください

結果: git commit

検索入力ページ

検索結果ページのモックアップ

検索入力の次は検索結果の表示です。まずはどんな検索条件に対しても常に同じ検索結果を返すページを作成しましょう。そのためのJSONデータを以下のプロンプトで準備します。

プロンプト(省略形)
ニュース記事とそのタイトルを3本作成し、JSONで出力してください。
3本の記事はそれぞれ政治、経済、外交のニュースで、日付は2025-01-01のみ、
JSONは以下の形式です。
[ 
  { "title":"xxxxx", ...(省略) },
  {...}, 
  {...} 
]
上記プロンプト全体と、AIからの出力
プロンプト
ニュース記事とそのタイトルを3本作成し、JSONで出力してください。
3本の記事はそれぞれ政治、経済、外交のニュースで、日付は2025-01-01のみ、
JSONは以下の形式です。
[
   {
      "title":"xxxxx",
      "article":"yyyyyyyyyyyyyy",
      "category":"経済",
      "date":"2025-01-01",
      "views":18050,
      "tags":[
         "自動車",
         "貿易",
         "投資",
         "AI"
      ]
   },
   {
      "title":"xxxxx",
      "article":"yyyyyyyyyyyyyy",
      "category":"外交",
      "date":"2025-01-01",
      "views":4025,
      "tags":[
         "米国",
         "ASEAN"
      ]
   }
]
AIからの出力
[
  {
    "title":"日本復活党、新たな農業政策を発表 - 食料自給率向上へ",
    "article": "日本復活党は本日、食料自給率の大幅な向上を目指す新たな農業政策を発表した。
                農水省と連携し、スマート農業技術の導入支援、若手農業従事者への補助金拡充、
                そして休耕地の有効活用を推進する。党首は「日本の食の安全保障を確立し、持
                続可能な農業を実現する」と述べた。",
    "category":"政治",
    "date":"2025-01-01",
    "views":28000,
    "tags":[
        "日本復活党",
        "農水省"
      ]
  },
  {
    "title":"AI技術が自動車産業を革新 - 新たな投資と雇用創出の期待",
    "article": "AI技術の急速な進化が、日本の自動車産業に大きな変革をもたらしている。大手自
                動車メーカーは、自動運転技術や生産ラインの最適化にAIを導入し、効率化とコス
                ト削減を実現。これにより、新たな研究開発への投資が活発化し、AI関連分野での
                雇用創出も期待されている。政府は、AI技術開発への支援を強化し、国際競争力の
                維持を目指す方針だ。",
    "category":"経済",
    "date":"2025-01-01",
    "views":18050,
    "tags":[
      "自動車",
      "貿易",
      "投資",
      "AI"
    ]
  },
  {
    "title":"日米ASEAN首脳会談、地域安全保障と経済連携を強化",
    "article": "202511日、日米ASEAN首脳会談が開催され、インド太平洋地域の安全保障と経
                済連携の強化について議論された。米国は、地域における自由で開かれた秩序の維
                持を強調し、日本とASEAN諸国は、海洋安全保障協力の推進とサプライチェーンの
                強靭化で合意。共同声明では、地域課題への共同対処と多国間主義の重要性が再確
                認された。",
    "category":"外交",
    "date":"2025-01-01",
    "views":4025,
    "tags":[
      "米国",
      "ASEAN"
    ]
  }
]

AIコーディングツールは会話形式でやりとりを続けられるので、直前の出力を参照してページを作成しましょう。

プロンプト
このJSON配列を元に、ニュース記事一覧を表示する検索結果ページを
@app/search/page.tsxとして作成してください。
ただし、クエリーパラメーターは考慮せず、どんな検索条件に対しても
この3つの要素を持つJSON配列をもとに固定された結果を表示してください。          

結果: git commit

検索結果ページ

結果ページ app/search/page.tsx は表示できたので、検索入力ページ app/page.tsx を修正します。ページ遷移型の検索フォームでは<input>要素は<form>で囲むのが慣習です。

プロンプト
@app/page.tsx で<input>と<button>を<form>で囲んでください。
アクションは<form action="/search">を指定して、formをsubmit
したら@app/search/page.tsxを表示してください。  

結果: git commit

app/page.tsx
import styles from './page.module.css';

// 検索入力ページ
export default function Home() {
  return (
    <div className={styles.container}>
      <form action="/search">
        <input type="text" placeholder="Search..." className={styles.searchInput} />
        <button type="submit" className={styles.searchButton}>Search</button>
      </form>
    </div>
  );
}

これで、検索入力ページからボタンを押すと検索結果ページに遷移するようになりました。

https://youtu.be/1nQp5AwURfU

ここで学んだこと

  • Next.jsの初期設定
  • 余分なコードをクリアしてまっさらな状態から始めるプロンプト
  • app/page.tsxapp/search/page.tsxによるNext.jsのファイルベース・ルーティング
  • ページ遷移を実現するために<form>, <input>, <button>を組み合わせること

コーディング: DB代わりにファイルから検索結果を返す

先ほど常に同じ検索結果を返すページを作成しました。ここからは検索条件を反映した検索結果を返します。しかし、いきなりDB(データベース)を導入すると複雑になりすぎるので、代わりにファイルを使いましょう。そうすればNext.jsとReactの機能だけで検索を実現できます。

ファイルからデータを読み込み

プロンプト
@app/search/page.tsxのJavaScript object配列を
@app/search/articles.jsonというファイルに移動して、
Node.jsのfilestream APIを使ってファイルの中身を読み込んでください。

結果: git commit

検索条件で絞り込むには、元のデータの件数が多いほうがわかりやすいので、記事数を30に増やし、また表示方法も増えた記事数に合わせます。

プロンプト
@app/search/articles.json記事の数を30に増やしてください。
また、@app/search/page.tsxによる検索結果の表示は、CSS gridを使い、
縦3列にカードを並べたギャラリー表示としてください。

結果: git commit、そのあと手作業で調整

これで以下のような検索結果が表示されます。

ファイルから検索結果を表示

URLの検索条件を検索結果に反映

検索条件はURLのクエリーパラメーターで表現されていますが、Next.jsでクエリーパラメーターを扱うには

があります。後者のuseSearchParamsuseから始まるのでReact hookであり、Next.jsが独自に定義しているものです。

今回はページ遷移型なのでpropsのsearchParamsを使います。

なぜ?ページ遷移型でpropsのsearchParamsを使う理由

ページ遷移型、つまりSPA的な動作を必要としないので、今回app/search/page.tsxはServer Componentとして実装しています。

つまりClient Component(=SPA型の動作をする)ではないため、useSearchParamshookは使えません。useSearchParamsを含むReact hooksを使えるのはClient Componentだけです。

プロンプト
@app/search/page.tsxでNext.jsのsearchParamsを使って、
URLのクエリーパラメーターで表現された検索条件を、検索結果に反映します。

async function getSearchResult(query: string) {}を実装し、
titleとarticleのいずれかにquery変数の文字列が含まれる場合検索結果に
含むものとします。

query変数が英語文字列を含む場合に対応するため、queryもtitleもarticle
もtoLowerCase()した上で検索結果に含むかどうかの判定をしてください。

結果: git commit、そのあと手作業とAI両方使って調整

https://youtu.be/SC59UN9dWkk

これで検索条件が検索結果に反映されるようになりました。

ちなみに、searchParamsの型がPromiseを含むことに注意してください。

app/search/page.tsx
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;

Next.js 15から導入された変更によりsearchParamsPromiseが要求されるようになりました。Promiseを忘れると以下のようなエラーが発生します。

Error: Route "/search" used searchParams.query. searchParams is a Promise and must be unwrapped with await or React.use() before accessing its properties. Learn more: https://nextjs.org/docs/messages/sync-dynamic-apis

Zod導入

ファイルを読み込むようにすると、ファイル内のデータが期待するデータ構造に従っているかわからなくなります。今回の例だと30記事のうち1記事だけtitleを忘れていた、ということもありえるわけです。

そういった不備を防ぐため、Zodを使ってarticles.jsonが本当にinterface Articleの型に従っているのかを確認します。

プロンプト
Zodを導入して @app/search/articles.json の中身がinterface
Articleに従っているかを確認してください。

結果: git commit

コーディング: 詳細検索

ここまで実装してきたのはニュース記事のtitlearticleによる検索です。

本記事のデータではcategoryも指定されているので、それを使った検索も実装します。

プロンプト
@app/search/page/.tsxで以下のような<form>を設置し、詳細検索を可能にします。

<form action="/search">
  <div>
    <input />
    <button type="submit">Search</button>
  </div>

  <details>
    <summary>詳細検索</summary>
  <details>
</form>

<input>要素は初期値としてURLのクエリーパラメーター`query=`の値を持ちます。
<details>の中はcategoryを表すチェックボックスがCSS gridを使って2次元
グリッド上に配置されています。各チェックボックスの初期値はURLの
クエリーパラメーター`category=`の値によって、チェックされているかどうか判定します。

クエリーパラメーター`category=`は複数指定可能で`category=xxx&category=yyy&category=zzz`
の形で指定するものとします。<details>はcategoryチェックボックスのいずれかが
チェックされている場合、すなわち有効なクエリーパラメーター`category=`が指定
されている場合open、そうでない場合はcloseとします。

結果: git commit

https://youtu.be/VB99D1AmHCg

コーディング: Prisma導入

いよいよデータベースから検索結果を返すためにPrismaを導入します。Prismaの導入は複数ステップに分かれるので、AIにまず計画を立ててもらいましょう。

AIによるプランニング

プロンプト
このプロジェクトにPrismaを導入します。いきなりソースコードを変更するのではなく、
@plans/prisma-plans.mdファイルを新たに作成し、そこにタスクリストを記載してください。

データベースに投入するデータは@app/search/articles.jsonです。
これをもとに必要なテーブルを洗い出し、seed用のスクリプトを作成します。

結果: git commit、そのあと手作業で調整。出来上がったplans/prisma-plans.mdの中身

タスク「1.Prisma初期設定」「2.スキーマ定義」

タスク1、2は以下のプロンプトだけで簡単に実行できます(plans/prisma-plans.md)。流れとしては公式ドキュメントのGetting Started (SQLite)と同じです。

プロンプト
プロンプト: @plans/prisma-plans.mdのタスク1と2を実行してください。
実行が完了したタスクのチェックボックスをチェックしたてください。

結果: git commit

タスク「3.マイグレーション」

タスク3も公式ドキュメントのGetting Started (SQLite)に書かれているものと同じ、初回のDBマイグレーションです(plans/prisma-plans.md)

2025年11月現在、Prisma CLIではRust-free engineはunstableなのでRust engineを使う

本記事ではPrisma ClientRust-free engineとESM-native clientという最新の機能を使っています。

一方でPrisma CLIにはRust-free engineではなく、クラシックなRust engineを使っています。その理由は公式のIssue 27403にあるように、まだPrisma CLIにはRust-free engineがstableではないからです。

実際に公式ドキュメント各所を見ながらこのようにprisma.config.tsを設定してみたのですが、

prisma.config.ts
// prisma.config.tsはprisma migrateなどのCLIコマンドで利用されます
//
//   https://www.prisma.io/docs/orm/reference/prisma-config-reference
//   > The Prisma Config file configures the Prisma CLI, including subcommands
//   > like migrate and studio, using TypeScript.
//   https://www.prisma.io/blog/announcing-prisma-6-18-0
//   > As we prepare for Prisma v7, we’re moving more functionality into the
//   > prisma.config.ts file so developers can adopt it and not be caught off guard.
//   > To assist with this, we’ve moved to make prisma init automatically create a
//   > config file when you start a new project.

// prisma migrate devコマンドで.envファイルを読み込むためにはdotenvが必要
import "dotenv/config";
import { defineConfig, env } from "prisma/config";
import { PrismaBetterSQLite3 } from "@prisma/adapter-better-sqlite3";

type Env = {
  DATABASE_URL: string;
};

// adapterの指定方法は以下参照
// https://www.prisma.io/docs/orm/prisma-client/setup-and-configuration/no-rust-engine#4-instantiate-prisma-client
const adapter = new PrismaBetterSQLite3({ url: env<Env>("DATABASE_URL") });

// 各プロパティの指定方法は以下参照
// https://www.prisma.io/docs/orm/reference/prisma-config-reference
export default defineConfig({
  schema: "prisma/schema.prisma",
  migrations: {
    path: "prisma/migrations",
  },
  engine: "js", // engine: "js"はRust-free engine(i.e. js-only)のこと
  experimental: {
    adapter: true, // engine: "js"の場合、この指定が必要です
  },
  // Promiseを返すという一風変わった指定が要求されます
  adapter: () => Promise.resolve(adapter),
});

このようなエラーが出てしまいました。schema.prismaが間違っていると言われます。

WARNING: Your schema specifies the following datasource properties but you are using a Driver Adapter via prisma.config.ts:
- url

The values from your schema will NOT be used!

We recommend you to remove those properties from your schema to avoid confusion if you are only using driver adapters.
Error: External error id#0

エラーに従ってschema.prismaからurlの指定を削除すると、

Error: Prisma schema validation - (validate wasm)
Error code: P1012
error: Argument "url" is missing in data source block "db".
  -->  prisma/schema.prisma:10

今度はurlがないからschema.prismaが間違っていると言われ、互いに矛盾する相互のエラーで解決不能になってしまいました。

プロンプト
@plans/prisma-plans.md タスク3を実行         

上記プロンプトで、AIは内部でnpx prisma migrate dev --name initを実行します。

Prisma config detected, skipping environment variable loading.
Prisma schema loaded from prisma/schema.prisma
Datasource "db": SQLite database "dev.db" at "file:./dev.db"

SQLite database dev.db created at file:./dev.db

Applying migration `20251102134723_init`

Your database is now in sync with your schema.

✔ Generated Prisma Client (6.18.0) to ./generated/prisma in 27ms

結果: git commit, generated Prisma Clientは別コミットに分割

タスク「4.シードスクリプトの作成」

DBのスキーマとPrisma Clientコードの生成は出来たので、次はファイルの中にあるデータをDBに投入するためのスクリプトを作成します。このようにデータベースに初期データを投入することを「シード」、それを行うスクリプトを「シードスクリプト」と呼びます。

ここからは私自身試行錯誤しながら書いていったので、以下のプロンプトを何度も使ってAIにタスクを実行してもらいつつ、手作業での修正も入れています。

プロンプト
@plans/prisma-plans.md のタスク「4.シードスクリプトの作成」の残りを一つずつ実行し、
サブタスク1つおわるごとに私に確認してください。

先にprisma-plans.mdをより詳細に書き出しました(git commit)。

次にシードスクリプト実行のためにtsxを導入します(git commit)。シードスクリプトseed.tsはNext.jsアプリケーション内部ではなくターミナルから実行するため、Next.jsとは別にTypeScriptを実行する環境を構築します。今回はNode.jsでコンパイルすることなくTypeScriptを実行でき、設定も簡単であることからtsxを使いました。

続いて以下の変更で、DBへ実際にデータをシードする前に、consoleにプリントして事前動作を確認します。

  • (git commit) articles.jsonを移動。prisma-plans.mdを更新し、より詳細なタスクに分割
  • (git commit) さらに4. シードスクリプトの作成タスクを詳細化
  • (git commit) カテゴリのシード処理とpackage.jsonのseedスクリプトを追加
  • (git commit) articles.jsonのZodスキーマ定義とCategoryのprint

ここからは実際にDBにシードする処理の記述です。

  • (git commit) Categoryのseed処理を完了
  • (git commit) シードスクリプトの全タスクを完了し、記事とカテゴリのシード処理を実装

本記事では最新のNo-Rust Engine及びESM-native Clientを使っているので以下の情報に従いました。

具体的には以下のコードでPrisma Clientのインスタンスを生成しています。

import { PrismaClient } from "../../generated/prisma/client";
import { PrismaBetterSQLite3 } from "@prisma/adapter-better-sqlite3";

const adapter = new PrismaBetterSQLite3({ 
  url: process.env.PRISMA_CLIENT_DATABASE_URL 
});
const prisma = new PrismaClient({ adapter });
AIに任せると最新機能の設定を反映してくれない

AIに任せる時は注意が必要で、高い確率でimport { PrismaClient } from "@prisma/client"というコードを生成します。これはPrismaにおける古い慣習で、node_modules以下に生成されるPrismaClientを利用するものですし、本記事のように最新の機能であるNo-Rust Engine及びESM-native Clientは使えません。

一般に、AIがまだ学んでいない最新機能や最新のコーディング慣習に従わなければならない時は注意しましょう。

seed.tsを見るとconnectOrCreateを使っています。この部分の書き方は難しいのですが以下を参考にしてください。

seed.ts
... 

await prisma.article.create({
  data: {
    ... 
    tags: {
      connectOrCreate: article.tags.map((tag) => ({
        //connectOrCreateの中にはwhereとcreateが必要
        where: {
          // whereに一致するものはconnect
          name: tag, //schemaのTagのフィールド, unique制約
        },
        create: {
          // 一致しなかったものはcreate
          name: tag, //schemaのTagのフィールド
        },
      })),
    },
  },
});

... 

これでDB内にデータを移すことが出来ました。

タスク「5.Next.js側でのPrisma Clientの利用」

最後はNext.jsとPrisma Clientを統合しましょう。

しかしこのままではエラーが出てしまいました。

 ⚠ ./node_modules/.pnpm/@prisma+adapter-better-sqlite3@6.18.0/node_modules/@prisma/adapter-better-sqlite3/dist
Package better-sqlite3 can't be external
The request better-sqlite3 matches serverExternalPackages (or the default list).
The request could not be resolved by Node.js from the project directory.
Packages that should be external need to be installed in the project directory, so they can be resolved from the output files.
Try to install it into the project directory by running npm install better-sqlite3 from the project directory.

同様のメッセージが better-sqlite3だけでなくbindingsfile-uri-to-pathパッケージでも発生しています。幸い、エラーメッセージがTry to install it into the project directory by running npm install better-sqlite3 from the project directory.と解決方法を案内してくれているので、それに従って以下を実行します。

npm install better-sqlite3
npm install bindings 
npm install file-uri-to-path

これで、問題なく動くようになりました。ページ遷移型の検索をNext.jsとPrismaを使って実装できました!

https://youtu.be/WB7GOBpb6bM

Discussion