👨‍💻

【Next】Next × Prisma × Vercel でポートフォリオを書き換える

2024/01/11に公開

初めに

今回は React で書いていたポートフォリオを Next, Prisma, Vercel を用いた実装に切り替えていきたいと思います。
それぞれのコードは以下にまとめているので、よろしければご覧ください。

書き換え前のリポジトリ
https://github.com/Koichi5/portfolio

書き換え後のリポジトリ
https://github.com/Koichi5/portfolio-next

なお、Next, Prisma, Vercel は今回初めて触れた技術で、歴も非常に浅いため、誤り等あれば指摘していただければ幸いです。

記事の対象者

  • Next 初学者
  • Next, Prisma, Vercel 学習者
  • React から Next に移行したい方

使用技術

以前の使用技術

  • React
  • TypeScript
  • Vite
  • GitHub Pages

今回の使用技術

  • React
  • TypeScript
  • Next.js
  • Prisma
  • Vercel

バージョン

  • React : 18.2.0
  • TypeScript : 5.3.3
  • Next.js : 13.5.6
  • Prisma : 5.7.1

目的

今では Flutter, Swift を中心にモバイルの技術を扱ってきて、Webに関しては多少触れた程度で、ポートフォリオサイトもテキストをベタ打ちにしているような状態だったので、書き直しました。
モバイルとの勝手の違いなども多少掴めたかと思うので、その辺りも共有できたらと思います。

実装

実装は以下の手順で進めていきたいと思います。

  1. Nextプロジェクト作成
  2. Vercel 導入
  3. Prisma 導入
  4. Vercel Postgres の設定
  5. Prisma の設定
  6. データモデルの追加
  7. API の定義
  8. データの追加
  9. UI実装
  10. デプロイ

1. Nextプロジェクト作成

Next.js とは

Next.js は、JavaScriptのフレームワークである「React」をベースにしたWebフレームワークになります。
ホームページによると、Reactを拡張することでフルスタックのWebアプリケーションを開発することができ、Rustに基づいて素早いページのビルドが可能とのことです。

プロジェクト作成

まずは Nextプロジェクトを作成します。
cd コマンドでプロジェクトを作成したいディレクトリに移動し、以下のコマンドを実行します。自分の場合だとnpm create next-appでも作成できました。

npx create-next-app@latest
# or
yarn create next-app

先述のコードを実行すると以下のような表示になるので、プロジェクト名やその他の設定を行います。

✔ What is your project named? … portfolio-next
✔ 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

「Success!」と表示されたら、以下のコードを実行して、「localhost:3000」にアクセスして、デフォルトのページが表示されているか確認してみましょう。

npm run dev

GitHub への Push

ローカルホストで正常に動作していることが確認できたら、GitHub にリポジトリを作成して、Push しておきましょう。
これでコードベースの Nextプロジェクトの作成は完了です。

サイトでのデプロイ

コマンド以外にも、Next.jsのサイトでもデプロイできるようです。
Next.jsのサイトに移動して以下の画像の「Deploy」を押します。

Git Scope としてプロジェクトを管理したい GitHub アカウントを選択し、リポジトリ名を入力して「Create」を押します。

以下のようにサイトが公開され、GitHubリポジトリも作成されていることがわかります。
数ステップでサイトの公開までできるのは初学者としては非常に嬉しいポイントですね。

2. Vercel 導入

Vercel とは

Vercel はフロントエンドエンジニアが、Webサイトやアプリケーションを簡単にデプロイ、ホストするためのPaaSサービスです。
デプロイ、ホスティング以外にも、データベースとしての機能もあります。
また、GitHubと連携することで、ソースコードが変更されるたびに自動的にデプロイが走るため、毎回デプロイのための手間も生じません。

Vercel でのデプロイ

まずは Next.js on Vercel のサイトに移動します。
以下の画像のような画面で「Start Deploying」を押します。

次に以下のような画面で、先ほど作成したNext.jsのプロジェクトのリポジトリを選択します。

最後に以下の画面で Vercel 上でのプロジェクトの名前を設定します。
ここは GitHubのリポジトリと同じような名前にした方が判別しやすいかと思います。
設定が終了したら「Deploy」を押して、Vercelでの設定は完了です。

Flutter で考えると

個人的には、Flutter で考えると、Firebase のような役割としてみると考えやすいかと思いました。

もちろん、Vercel は PaaS であり、アプリケーションのデプロイやパフォーマンス最適化に重点を置いているのに対して、Firebase は BaaS であり、データベース操作やユーザー認証などのバックエンド機能を提供する点に重点を置いているため異なりますが...

デプロイ、ホスティングに関しては Firebase Hosting、データベースに関しては Cloud Firestore のようなイメージでしょうか。

3. Prisma 導入

Prisma とは

Prisma は公式サイトによると、「次世代の Node.js と TypeScript の ORM 」であると定義されていました。Prismaを使用することで、開発者がデータベースを使用する際に直感的なデータモデル、自動マイグレーション、タイプセーフ、自動補完機能などを利用できます。
そもそも ORM とは、こちらのサイトの説明にある通り、オブジェクト関係マッピング(Object-Relational Mapping)の略であり、オブジェクト指向プログラミングとリレーショナルデータベースの互換性を向上させるための技術です。
オブジェクト指向言語のクラスとデータベースのテーブルを合致させることで、SQLを書くことなくデータの操作が可能になります。

Prisma インストール、設定

まずは以下のコードで Prisma パッケージのインストールを行います。

npm install prisma --save-dev

次に以下のコードを実行して Prisma の初期設定を行います。
今回は Vercel の方で PostgreSQL のデータベースを使用したいので、以下のように --datasource-provider postgresql としておきます。

npx prisma init --datasource-provider postgresql

なお、 datasource-provider には以下の6つのデータソースがあり、幅広いくサポートしています。

  • postgresql
  • mysql
  • sqlite
  • sqlserver
  • mongodb
  • cockroachdb

init コマンドを実行するとプロジェクト内に以下の二つが作成されます。

  • prisma / schema.prisma
  • .env

それぞれの内容は以下のようになっています。

prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}
.env
DATABASE_URL="file:./dev.db"

Webアプリケーションで使用するモデルは schema.prisma に記述していきます。
また、.env ファイルは GitHub 上に公開しないように、.gitignoreに登録しておくようにしましょう。

Prisma のコマンドに関してはPrisma CLI Referenceのサイトがわかりやすく、ここで紹介していない機能も多くあります。

4. Vercel Postgres の設定

Postgres データベース作成

次に Vercel でデータを保存するために Postgres Storage を追加します。
まずは以下のように Vercel のダッシュボードに移動し、「Storage」タブを選択します。

Storage タブで「Create Database」を押し、以下のような画面で「Postgres」を選択して「Continue」を押します。

最後にデータベース名とリージョンを指定してデータベースの設定が完了します。
なお、リージョンでは日本がないため、シンガポールに設定しておきましょう。

プロジェクトとデータベースの連携

データベースの設定が完了したらプロジェクトとデータベースの接続を行います。
Storage タブで「Connect Project」を押して、先程作成したプロジェクトを選択することでプロジェクトとデータベースを接続することができます。

プロジェクトとデータベースの接続に関しては以下のコードを実行して、接続することもできます。

vercel link

環境変数の変更

プロジェクトとデータベースの連携が完了したら、作成したデータベースの情報をローカルに反映させます。
以下のコマンドで作成したデータベースの環境変数をローカルに反映させることができます。

vercel env pull .env

コマンドを実行した後 .env ファイルを見てみると、以下のように必要な環境変数が反映されていることがわかります。(実際には値が入っている部分を必要に応じて「*****」に変更しています。)

.env
# Created by Vercel CLI
NX_DAEMON=""
POSTGRES_DATABASE="verceldb"
POSTGRES_HOST="*****"
POSTGRES_PASSWORD="*****"
POSTGRES_PRISMA_URL="*****"
POSTGRES_URL="*****"
POSTGRES_URL_NON_POOLING="*****"
POSTGRES_USER="default"
TURBO_REMOTE_ONLY=""
TURBO_RUN_SUMMARY=""
VERCEL="1"
VERCEL_ENV="development"
VERCEL_GIT_COMMIT_AUTHOR_LOGIN=""
VERCEL_GIT_COMMIT_AUTHOR_NAME=""
VERCEL_GIT_COMMIT_MESSAGE=""
VERCEL_GIT_COMMIT_REF=""
VERCEL_GIT_COMMIT_SHA=""
VERCEL_GIT_PREVIOUS_SHA=""
VERCEL_GIT_PROVIDER=""
VERCEL_GIT_PULL_REQUEST_ID=""
VERCEL_GIT_REPO_ID=""
VERCEL_GIT_REPO_OWNER=""
VERCEL_GIT_REPO_SLUG=""
VERCEL_URL=""

5. Prisma の設定

次は Prisma との連携を行います。
Vercel の Storage タブを開き、「Prisma」の部分を選択すると以下のような表示になります。
これは Prisma を使って Vercel の Postgres ストレージにアクセスする際の設定方法を表しています。

上記のコードをコピーして、schema.prisma を開き、ペーストして以下のように変更します。

schema.prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url = env("POSTGRES_PRISMA_URL") // uses connection pooling
  directUrl = env("POSTGRES_URL_NON_POOLING") // uses a direct connection
}

Vercel から pull した .env ファイルの変数を上記のように url, directUrl で参照し、それを db とすることで Next, Prisma, Vercel の3つの連携が完了します。

6. データモデルの追加

データモデルの定義

次は Prisma でアプリに必要なデータモデルの設定を行います。
今回はポートフォリオのプログラミング言語ごとの熟練度のデータを SkillLanguageProficiency としてデータモデルを作成していきます。
データモデルの定義は shema.prisma で行います。

schema.prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url = env("POSTGRES_PRISMA_URL") // uses connection pooling
  directUrl = env("POSTGRES_URL_NON_POOLING") // uses a direct connection
}

model SkillLanguageProficiency {
  id          Int    @id @default(autoincrement())
  name        String
  proficiency Int
}

Prisma の Model 定義に関しては公式ドキュメントにまとめてありました。詳細な実装はこちらをご参照ください。

マイグレーションとクライアント生成

モデルの定義が完了したら以下のコードを実行します。
以下のコードでは開発環境でマイグレーションを生成できます。
--name でマイグレーションの名前をつけることができます。
今回は初めてのマイグレーションであるため、initとしています。

npx prisma migrate dev --name init

次に以下のコマンドで Prisma Client の生成を行います。
schema.prisma に基づいて Prisma Client のコードを生成し、データベース操作を容易にするための機能を提供します。

npx prisma generate

なお、npx prisma migrate dev の代わりに以下のコマンドを用いることもあります。開発初期またはマイグレーションファイルが必要ないときに使用します。

npx prisma db push

データベースに正常に反映されると以下のような表示になります。

🚀  Your database is now in sync with your schema.

7. API の定義

ルートディレクトリに pages ディレクトリを作成し、さらに pages ディレクトリの中に api ディレクトリを作成します。

api ディレクトリの中に、Prismaのデータを取得するためのコードを記述します。ファイル名をキャメルケースで定義するのは抵抗がありますが、以下のコードのようにします。

pages/api/skillLanguageProficiencies.ts
import { PrismaClient } from "@prisma/client";
import { NextApiRequest, NextApiResponse } from "next";

const prisma = new PrismaClient();

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method == "GET") {
    try {
      const skillLanguageProficiencies =
        await prisma.skillLanguageProficiency.findMany();
      res.status(200).json(skillLanguageProficiencies);
    } catch (e) {
      res.status(500).json({ error: "Internak Server Error" });
    }
  } else {
    res.setHeader("Allow", ["GET"]);
    res.status(405).end(`Method ${req.method} Not Allowed`);
  }
}

上記ではGETメソッドのみの実装を行なっています。
const prisma = new PrismaClient(); としてクライアントを定義し、prisma.skillLanguageProficiency.findMany(); とすることで、prisma に定義されているデータを参照することができ、findMany() メソッドを使用することで保存されているデータを取得することができます。

個人的には、ディレクトリ構造によってAPIのルートが変わるといったように、ディレクトリ構造が機能自体に直接作用するような実装はしたことがなかったので、新鮮でした。

8. データの追加

Postgres Storage へのデータ追加

次に Vercel の Postgres Storage にデータを追加します。
Storageタブのページに以下のような部分があります。
「Query」 の部分で SQL を実行することで Vercel にデータを追加することができます。

データを追加するコードは以下の通りです。

INSERT INTO
    "SkillLanguageProficiency" (name, proficiency)
VALUES
    ('Dart', 5),
    ('Swift', 4),
    ('Python', 4),
    ('JavaScript', 3),
    ('TypeScript', 3),
    ('R', 1),
    ('Unity', 1)

「Query」に入力したコードは、ページをリロードすると消えてしまうので、プロジェクト内に SQLファイルを作成して書いておくのも良いかもしれません。

正常に追加できたら以下のように「Browse」の部分でデータを確認することができます。

Prisma Studio での参照

また、追加したデータは以下のコードを実行することで、Prisma Studio で参照、追加することができます。

npx prisma studio

Prisma Studio は以下のような見た目になります。
「Add record」でデータを追加することもできます。

9. UI実装

今までの実装で Prisma でのデータモデル定義、Vercel上でのデータの追加、データ取得のためのAPI実装が完了しました。
いよいよ取得したデータをUIに反映させたいと思います。
コードは以下のようになります。

skills_languages.tsx
import { useEffect, useState } from "react";
import { SkillLanguageProficiency } from "@prisma/client";
import SubTitle from "@/components/sub_title";
import TableItem from "@/components/table_item";

const SkillsLanguages = () => {
  const [languages, setLanguages] = useState<SkillLanguageProficiency[]>([]);
  const [loading, setLoading] = useState(false);

  const fetchSkillsLanguages = async () => {
    setLoading(true);
    try {
      const response = await fetch("/api/skillLanguageProficiencies");
      if (!response.ok) {
        throw new Error("Network response was not ok");
      }
      const data = await response.json();
      setLanguages(data);
    } catch (error) {
      console.error("Failed to fetch about profiles:", error);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    fetchSkillsLanguages();
  }, []);

  return (
    <div style={{ paddingBottom: 40 }}>
      <SubTitle text={"Languages"} />
      <table
        style={{ alignSelf: "left", alignContent: "left", alignItems: "left" }}
      >
        {languages.map((language) => (
          <div key={language.id}>
            <TableItem
              keyValue={language.name}
              Value={
                " ★ ".repeat(language.proficiency) +
                " ☆ ".repeat(5 - language.proficiency)
              }
              paddingBetween={230}
            />
          </div>
        ))}
      </table>
    </div>
  );
};

export default SkillsLanguages;

fetchSkillsLanguages では先程作成したAPIを fetch 関数で叩いています。
この fetch 関数で叩くAPIのルートは pages ディレクトリの中に定義されているルートに依存します。
データを取得する fetchSkillsLanguages 関数を useEffect で実行することで、レンダリングされた際に一度実行されます。

そして、useEffect で取得したデータを setLanguages に渡します。
こうすることで、languages を参照することでデータをページで使用できるようになります。

localhost で実行してみると以下のようにデータが表示されているかと思います。

10. デプロイ

今までのステップでローカルホストにおけるデータの取得、表示はできるようになりました。
最後にデプロイした状態でも正常に表示されるようにします。
Vercel は GitHub と連携しているため、コードの変更内容をプッシュした時点で自動デプロイが走りますが、Prisma を使用している関係で多少変更する必要があります。

package.jsonbuild の内容を以下のように変更します。
Vercelへのデプロイのページにもあるように、Vercel はデプロイの際に依存関係を自動的にキャッシュしますが、Prismaを使用している場合は Prisma Client が前のバージョンのままになる可能性があります。
その問題を解決するために prisma generate を追加して、build が走ったときに同時に Prisma Client も生成するようにしています。

package.json
  "scripts": {
    "dev": "next dev",
    "build": "prisma generate && prisma db push && next build", // build を変更
    "start": "next start",
    "lint": "next lint"
  },

この状態で変更したコードを GitHub にプッシュすると正常にデプロイができるかと思います。
以上でデプロイまでが完了しました。

まとめ

最後まで読んでいただいてありがとうございました。
今回は Next, Prisma, Vercel に初めて触れたので、それを簡単にまとめました。

以前 React で挫折したことがあり、多少苦手意識があったのですが、Prisma や Vercel を用いて比較的簡単にデプロイまでできたので、前よりも開発のハードルが下がっていると感じました。

誤っている点や他の実装方法等あればご指摘いただけると幸いです。

参考

Next.js
https://nextjs.org/

Next.js, Prisma, Vercel
https://vercel.com/guides/nextjs-prisma-postgres

https://zenn.dev/msy/articles/8d991c79b739aa

https://hisuiblog.com/nextjs-use-prisma-postgresql-deploy-to-vercel/

Prisma
https://www.sddgrp.co.jp/blog/technology/use-next-jsprisma/

Prisma CLI Reference
https://www.prisma.io/docs/orm/reference/prisma-cli-reference

Prisma Docs Models
https://www.prisma.io/docs/orm/prisma-schema/data-model/models

ORM
https://qiita.com/minimabot/items/0a3fcc41fd7140dfdc41

Discussion