🍊

Next.js14をVercelにデプロイするまで。+v0+Prisma+Neon

2024/09/03に公開

はじめに

Next.js14(AppRouter)を使用したアプリケーション開発の環境構築を行います。
以下の工程で作業していきます。

  1. Next.jsのセットアップ
  2. v0 by Vercelのセットアップ
  3. Prismaのセットアップ(DBはNeonを使用しました)
  4. Vercelへデプロイ
  5. (おまけ)ローカルデバッグの準備

v0に関してはshadcn/uiでも良かったのですが、最近巷で話題ということで組み入れてみました。(とても優秀✨)
Neonに関してはデータベースだったら何でも良いのですが、無料枠の制限が比較的緩かったのでこちらを使用しました。
Vercelに関してもHobbyプランを使用しているので、今回の構成は全て無料です。

今回使用したソースコード

https://github.com/ishiyama0530/worksample-nextjs14/tree/zenn-article-cbf7a0ee0d4a0e

Next.jsのセットアップ

Next.js本体の準備

公式に則り npx create-next-app@latestで開始します。

$ npx create-next-app@latest
✔ What is your project named? … worksample-nextjs14
✔ Would you like to use TypeScript? … No / Yes
✔ Would you like to use ESLint? … No / Yes
✔ Would you like to use Tailwind CSS? … No / Yes
✔ Would you like to use `src/` directory? … No / Yes
✔ Would you like to use App Router? (recommended) … No / Yes
✔ Would you like to customize the default import alias (@/*)? … No / Yes

Biomeを使用するのでESLintは「No」を選択します。

npm run devで見慣れた画面が表示されます。

Biomeの準備

Biomeも公式にある手順で初めていきます。

npm install --save-dev --save-exact @biomejs/biome
npx @biomejs/biome init

Biomeの設定ははこちらの記事のものをお借りしました 👏

https://qiita.com/aki-y/items/3e36f4eff477004e2197#biomeセットアップ

biome.json
{
  "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
  "files": {
    "ignore": ["node_modules", "public", ".next"]
  },
  "organizeImports": {
    "enabled": true
  },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true,
      "correctness": {
        "useHookAtTopLevel": "error",
        "noUnusedImports": "warn"
      }
    }
  },
  "formatter": {
    "enabled": true,
    "formatWithErrors": false,
    "indentStyle": "space",
    "indentWidth": 2,
    "lineWidth": 80,
    "lineEnding": "lf",
    "ignore": []
  }
}
.vscode/settings.json
{
  "[javascript]": {
    "editor.defaultFormatter": "biomejs.biome",
    "editor.formatOnSave": true,
    "editor.codeActionsOnSave": {
      "quickfix.biome": "explicit",
      "source.organizeImports.biome": "always"
    }
  },
  "[typescript]": {
    "editor.defaultFormatter": "biomejs.biome",
    "editor.formatOnSave": true,
    "editor.codeActionsOnSave": {
      "quickfix.biome": "explicit",
      "source.organizeImports.biome": "always"
    }
  },
  "[typescriptreact]": {
    "editor.defaultFormatter": "biomejs.biome",
    "editor.formatOnSave": true,
    "editor.codeActionsOnSave": {
      "quickfix.biome": "explicit",
      "source.organizeImports.biome": "always"
    }
  },
  "typescript.preferences.importModuleSpecifier": "non-relative",
  "editor.tabSize": 2
}

v0 by Vercelのセットアップ

こちらは公式のNext.jsのインストールは飛ばして、npx v0@latest initを実行します。

$ npx v0@latest init

以下のパッケージが依存関係に追加され、components.jsonが作成されます。

package.json
    "@radix-ui/react-icons": "^1.3.0",
    "class-variance-authority": "^0.7.0",
    "clsx": "^2.1.1",
    "lucide-react": "^0.438.0",
    "tailwind-merge": "^2.5.2",
    "tailwindcss-animate": "^1.0.7"

v0の動作確認を行います。v0の生成ページに移動して(何でも良いですが…)以下のプロンプトを実行します。

プロンプト
以下の要素を持つスレッドの詳細とレス一覧をもつページを作成してください。
これは掲示板アプリケーションの一部です。

- スレッドタイトル
- レス一覧
- レス投稿フォーム
- 30件ごとのページネーション

3つのコンポーネントが生成されるので、その中で自分のイメージに近いものを選択します。
その後、必要に応じて追加のプロンプトを打ち込んでデザインを調整します。
調整が済んだら右上の「Code </>」のボタンをクリックし、npxコマンドをコピーします。

ローカル環境でコピーしたコマンドを実行します。するとコンポーネント名を聞かれるので適当なコンポーネント名を入力します。(今回はThreadContentにしました)

$ npx v0 add hQP91eAUXkN
✔ What should we name the component? … ThreadContent
✔ Done.

The component has been added to your project.
To install theme colors and fonts, visit https://v0.dev/t/hQP91eAUXkN and follow the instructions under the Code tab.

その後、components配下にthread-content.tsxが作られます。
実際にpage.tsxにインポートし実行すると、先程v0の画面で調整していたコンポーネントが表示されていることを確認できます。

src/app/(application)/threads/[id]/page.tsx
import { ThreadContent } from "@/components/thread-content";

export type ThreadDetailPageProps = { params: { id: string } };

export default function ThreadDetailPage({
  params: { id },
}: ThreadDetailPageProps) {
  return <ThreadContent />;
}

Prismaのセットアップ

以下のコマンドでprismaとprisa-client両方をインストールします。

$ npm install prisma @prisma/client

以下のコマンドにより、.envschema.prismaが作成されます。

$ npx prisma init

データベースにはServerless Postgresを謳っているNeonを使用します。PrismaからNeonを使用するためのドキュメントが提供されていました。

データベースで他のアクション (移行など) を実行するために Prisma CLI を使用したい場合は、 directUrl プロパティで使用する DIRECT_URL 環境変数を追加する必要があります。 CLI が直接接続文字列 (PgBouncer なし) を使用するように、Prisma スキーマの datasource ブロックを追加します。

ドキュメントにあるように、.envに「DIRECT_URL」を追加し、schema.prismadirectUrl = env("DIRECT_URL")を追加します。

sample.env
# Connect to Neon with Pooling.
DATABASE_URL=postgres://daniel:<password>@ep-mute-rain-952417-pooler.us-east-2.aws.neon.tech/neondb?sslmode=require

# Direct connection to the database used by Prisma CLI for e.g. migrations.
DIRECT_URL="postgres://daniel:<password>@ep-mute-rain-952417.us-east-2.aws.neon.tech/neondb"
schema.prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider  = "postgresql"
  url       = env("DATABASE_URL")
+  directUrl = env("DIRECT_URL")
}

model Thread {
  id          String   @id
  title       String
  description String
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt()
}

データベースにスキーマの内容を反映します。

npx prisma db push

Neonのサイトからテーブルの内容が確認できます🐘
無事スキーマの変更が反映されたので、確認用にレコードを1つ追加しておきます。

サーバーコンポーネントで、Prismaによるデータベース接続ができることを確認します。
上記で追加したレコードが取得できることを確認します。

src/app/(application)/threads/[id]/page.tsx
import { ThreadContent } from "@/components/thread-content";
import { PrismaClient } from "@prisma/client";

export type ThreadDetailPageProps = { params: { id: string } };

const getThread = async (id: string) => {
  try {
    const prisma = new PrismaClient();
    const thread = await prisma.thread.findFirst();

    return thread;
  } catch (e) {
    return null;
  }
};

export default async function ThreadDetailPage({
  params: { id },
}: ThreadDetailPageProps) {
  const thread = await getThread(id);
  return (
    <>
      <ul className="text-center p-8">
        <li>{thread?.id}</li>
        <li>{thread?.title}</li>
        <li>{thread?.description}</li>
      </ul>
      <ThreadContent />;
    </>
  );
}

追加したレコードが描画されていることが確認できたので…よし。

Vercelへデプロイ

デプロイ設定

GitHub上にあるリポジトリのmainブランチへのpushをデプロイトリガーとします。
「Add New...」のドロップダウンから「Project」をクリックし、以下の設定ページに遷移します。

  1. GitHubと連携し、対象のリポジトリをインポート
  2. Configure Projectで.envの必要項目を転写
  3. Deployクリック

デプロイが成功すると紙吹雪でお祝いしてもらえます🎊

データベース接続エラー解決

どうやらデータベース接続がうまく行ってないようです。
ログを確認したところ以下のエラーが出ていました。

Prisma has detected that this project was built on Vercel, which caches dependencies. This leads to an outdated Prisma Client because Prisma's auto-generation isn't triggered. To fix this, make sure to run the prisma generate command during the build process.

Learn how: https://pris.ly/d/vercel-build

上記のURLの内容を要約すると…

  1. Vercelはデフォルトでモジュールをキャッシュするからビルドの度、prisma generateが実行されるようにして
  2. そもそもpostinstallが存在しないときはそれを追加して

ということなので、package.jsonに以下を追加しました。

...
  "scripts": {
    "postinstall": "npx prisma generate",
    "build": "next build",
...

上記を行い、mainブランチにプッシュすると、デプロイが実行され、データベース接続が想定通り行えていることを確認できます🚀

以上で、Next.jsのセットアップからVercelへデプロイする一連の作業は完了です。
お疲れ様でした!

(おまけ)ローカルデバッグの準備

ローカルデバッグでブレークポイント使えるようにしておきます。
vscodeでブレークポイントを使用している人は少ない印象です。私はサーバーサイドの開発においてブレークポイントは必須だと思っています…。(こう思うのは私がC#出身だからでしょうか?)

.vscode/launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Launch to Next.js",
      "runtimeExecutable": "npm",
      "runtimeArgs": ["run", "dev"],
      "restart": true,
      "console": "integratedTerminal",
      "localRoot": "${workspaceFolder}",
      "env": {
        "NODE_ENV": "development",
        "NODE_OPTIONS": "--inspect"
      }
    },
  ]
}

上記の設定を入れることでnpm run devしなくてもF5キーで実行されるようになります。
また、ブレークポイントを貼ったところで、変数の中身なども確認できるようになります。

最後に

最近はどこのサービスも公式ドキュメントが読みやすくて良いですね。
次回はこの構成の上にAppRouterを使用したアプリケーション作っていきます。

Discussion