React × SupabaseプロジェクトをVercelでデプロイ(webpack + TypeScript + React)

2024/01/06に公開

まえがき

「フロントエンドの処理は整った!でもデータベースもサーバーも設定しないとアプリとして公開できない...」「バックエンドは簡単に構築したい...」そんな願いをかなえるのが、BaaS( = Backend as a Service)のひとつ、「Supabase」 です。
本記事は、そんなSupabaseの入門記事として、また 「React×Supabaseプロジェクトをいかにして公開するのか?」 を解決する記事としてお届けします。

本記事で解説すること

  • 技術を予習する
    1. Supabaseとは?
    2. Vercelとは?
  • 手順
    1. GitHubリポジトリとローカルのプロジェクトを紐づける
    2. webpack + TypeScript + React のプロジェクトを用意
    3. Supabaseでデータベースを構築
    4. Supabaseでバックエンドを構築
    5. Vercelでデプロイ

技術を予習する

Supabaseとは?

バックエンド機能はこれ1つで

Supabaseは、これ1つでアプリに必要な様々なバックエンドの機能を提供するサービス。開発工数を大幅カットしてくれます。データベース・認証・ストレージ・リアルタイム更新など、全てがここに。

PostgreSQLベースな、Firebaseの代替

そんなSupabaseは2020年から提供されており、同じくアプリケーション開発プラットフォームである 「Firebase」の代替として注目が集まっています。
その一番の違いは、FirebaseがNoSQL(SQLを用いない)であるのに対して、SupabaseはRDBMSであるPostgreSQLを選択している点です。
確かに、慣れてしまえばFirebaseも便利ではありますが、独自の作法を学ばなければならず、扱えるようになるには一定の学習コストが必要です。
一方で、SQLを扱ったことがあれば親しみやすく、何よりSQL文によって複雑な検索や処理も可能となります。

Vercelとは?

Webアプリケーションを簡単にデプロイ

Vercelは、Webアプリケーションを簡単にデプロイできるプラットフォームです。

アプリケーションをデプロイ(= Web上に配置)するためには、サーバーが必要になります。マンションを建てるのに土地が必要なのと似ています。といっても、自力でサーバーを立てる(≒ 土地を整備する)のは大変なので、AWSなどのクラウドサーバーを借りるのが一般的です。
しかし、それでもWebアプリを公開する工程は簡単ではありません。200を超えるAWSのサービスから適切なものを選択し、正しく設定する必要があります。セキュリティや料金も考慮しなくてはなりません。
そんな 「デプロイ」を一瞬で済ませてしまうのがVercelです。

GitHubとのシームレスな統合

VercelはGitHubリポジトリと連携しており、ソースコードに変更があるたびに自動的に更新されます。そのため、開発者は継続的なデプロイを手動で管理する必要がありません。

手順

さっそく作業に移っていきましょう。

1. GitHubリポジトリとローカルのプロジェクトを紐づける

ローカル環境でプロジェクトディレクトリを作成

まずはご自身のローカル環境でプロジェクトディレクトリreact-supabase-todo-appを作成し、コードエディタ(VSCode)で開きましょう。

GitHubで新規リポジトリを作成・紐づけ

GitHubで新規リポジトリを作成
次にGitHubを開き、右上の「NEW」と書かれたボタンをクリックし、リポジトリ名を記入して新規リポジトリを作成します。
新規リポジトリ作成後
作成後、上記のような画面になるため、...or create a new repository on the command lineにある以下のコマンドをコピーし、ローカル環境でターミナルを開き、プロジェクトディレクトリ上(...\react-supabase-todo-app> )で実行します。

Terminal
echo "# リポジトリ名" >> README.md
git init
git add README.md
git commit -m "first commit"
git branch -M main
git remote add origin https://github.com/アカウント名/リポジトリ名.git
git push -u origin main

これで紐づけは完了です。コードを編集した際はコミットしましょう。

2. webpack + TypeScript + React のプロジェクトを用意

僕はwebpackを採用していますが、Create-React-AppでもViteでも以下の手順に大きな影響はありません。(※Vercelのビルド設定が異なります。)任意のプロジェクトをご用意ください。

今回、僕は以下のようなディレクトリ構成から開始します。

react-supabase-todo-app
└── src/
    ├── components/
    │   ├── Header.tsx
    │   ├── InputTodo.tsx
    │   ├── TodoApp.tsx
    │   └── TodoItem.tsx
    ├── css/
    │   ├── reset.css
    │   └── style.css
    ├── App.tsx
    ├── index.html
    └── main.tsx

① .gitignoreファイルの作成

プロジェクトディレクトリに.gitignoreを作成します。
.gitignoreは、Gitで管理されているプロジェクトの中で特定のディレクトリやファイルを除外する役割を持ちます。

Terminal (mac OS)
touch .gitignore

or

Terminal (windows OS)
ni .gitignore

.gitignoreに以下のコードを記述します。

.gitignore
# dependencies
/node_modules

# production
/dist

# env files
.env
.gitignoreの記述の詳細
  • 「#」以降の記述はコメントアウトされます。
  • ローカル環境で作成したnode_modulesbuildファイルはあくまで開発環境のもので、あらためてデプロイされた環境でnpm install``npm startが実行されることで生成されます。そのためgitにコミットする際は無視する必要があります。
  • .envは環境変数をローカル環境でシークレットに管理するファイルのため、gitにコミットしません。

② Node.js(npm)が導入されているか確認

Terminal
node -v

vX.X.Xのような実行結果が反映されれば既にインストール済みです。

Terminal
npm -v

X.X.Xのような実行結果が反映されれば既にインストール済みです。

③ package.jsonを作成

以下のコマンドで初期化処理を行います。

Terminal
npm init

今回は詳細な設定は不要です。何も入力せずEnterキーを押し続けましょう。
package.jsonが作成されます。

④ Reactのインストール

Terminal
npm i react react-dom
コマンドの詳細
  • npm i: npm installのこと。node_modulesディレクトリが作成され、インストールされたパッケージはpackage.jsondependenciesに記載されます。

⑤ Bootstrapのインストール(任意)

CSSフレームワークです。プロジェクトで使用する場合はインストールしましょう。

Terminal
npm i bootstrap bootstrap-icons react-bootstrap

⑥ TypeScriptを使用するために必要なパッケージのインストール

JavaScriptではなくTypeScriptを使用する場合はインストールする必要があります。
開発環境に限定してインストールします。

Terminal
npm i -D typescript @types/react @types/react-dom @babel/preset-typescript ts-loader
コマンドの詳細
  • npm i -D: npm install --save-devのこと。node_modulesにインストールされたパッケージはpackage.jsondevDependenciesに記載され、開発環境のみでの使用が可能となります。

⑦ webpack関連パッケージのインストール

開発環境に限定してインストールします。

Terminal
npm i -D webpack webpack-cli webpack-dev-server html-webpack-plugin dotenv-webpack @babel/core @babel/preset-env @babel/preset-react babel-loader css-loader style-loader

⑧ webpackを設定

プロジェクトディレクトリにwebpack.config.jsを作成します。

Terminal (mac OS)
touch webpack.config.js

or

Terminal (windows OS)
ni webpack.config.js

webpack.config.jsに以下のコードを記述します。

webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require("webpack");
const env = require("dotenv").config().parsed;

module.exports = {
    mode: 'development',
    entry: {
        main: __dirname + '/src/main.tsx',
    },
    output: {
        path: __dirname + '/dist',
        filename: '[name].js',
    },
    module: {
        rules: [
            {
              test: [/\.ts$/, /\.tsx$/],
              exclude: /node_modules/,
              use: [
                {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'],
                    },
                },
                {
                    loader: 'ts-loader',
                },
              ],
            },
            {
              test: /\.css$/,
              use: ['style-loader', 'css-loader']
            }
        ],
    },
    resolve: {
        modules: [__dirname + '/node_modules'],
        extensions: [".ts", ".tsx", ".js"],
    },
    plugins: [
	new HtmlWebpackPlugin({
	template: __dirname + '/src/index.html',
	}),
	env !== undefined
      ? new webpack.DefinePlugin({
          "process.env": JSON.stringify(process.env),
        })
      : new webpack.DefinePlugin({
          "process.env.REACT_PUBLIC_SUPABASE_URL": JSON.stringify(
            process.env.REACT_PUBLIC_SUPABASE_URL
          ),
          "process.env.REACT_PUBLIC_SUPABASE_ANON_KEY": JSON.stringify(
            process.env.REACT_PUBLIC_SUPABASE_ANON_KEY
          ),
        }),
    ],
    devServer: {
        static: {
            directory: __dirname + '/dist',
        },
        port: 8080,
    },
};

⑨ tsconfig.jsonの作成と変更

プロジェクトディレクトリの直下で以下のコマンドを実行するとtsconfig.jsonが作成されます。

Terminal
tsc --init

tsconfig.jsonに以下のコードを記述します。

tsconfig.json
{
  "compilerOptions": {
    "target": "esnext",
    "module": "commonjs",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "noUnusedParameters": true,
    "noUnusedLocals": true,
    "noImplicitReturns": true,
    "jsx": "react",
    "moduleResolution": "node",
    "allowSyntheticDefaultImports": true,
  },
  "include": ["src/**/*.ts", "src/**/*.tsx"],
  "exclude": ["node_modules"]
}
tsconfig.jsonの項目の詳細
  • "target": "esnext": 最新のJSに準拠したコードを出力させる。
  • "module": "commonjs": 出力したJavaScriptがモジュールを"Common.js"に準拠して読み込ませる。
    などの項目がある。

⑩ 開発サーバーの起動準備

scriptsプロパティにstartdevプロパティを追加します。

package.json
"scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "start": "npx webpack --config webpack.config.js",
        "dev": "npx webpack serve --config webpack.config.js"
},

以下のようなディレクトリ構成になっているか確認します。

frontend
├── node_modules/
├── src/
│   ├── components/
│   │   ├── Header.tsx
│   │   ├── InputTodo.tsx
│   │   ├── TodoApp.tsx
│   │   └── TodoItem.tsx
│   ├── css/
│   │   ├── reset.css
│   │   └── style.css
│   ├── App.jsx
│   ├── index.html
│   └── main.jsx
├── .gitignore
├── package-lock.json
├── package.json
├── README.md
├── tsconfig.json
└── webpack.config.js

3. Supabaseでデータベースを構築

Supabaseにサインアップし、プロジェクトを作成

supabase
GitHubアカウントでSupabaseにログインします。

プロジェクトを作成
プロジェクト名、パスワード、地域を入力し、プロジェクトを作成します。
するとダッシュボードの表示に切り替わります。

.envファイルの作成

プロジェクトディレクトリ直下に、.envファイルを作成しましょう。

Terminal (mac OS)
touch .env

or

Terminal (windows OS)
ni .env

そして、以下の通りに記述しましょう。

.env
SUPABASE_URL=
SUPABASE_ANON_KEY=

そして、Supabaseのプロジェクトダッシュボードの「Project API」セクションから「Project URL」「API Key」をコピーし、それぞれ「SUPABASE_URL」「SUPABASE_ANON_KEY」に代入しましょう。

テーブルの作成

Table Editor
左のタブから 「Table Editor」 を選択し、「Create a new table」でテーブルを作成しましょう。
今回は、テーブル名を「todo_items」、columnsを添付画像の通りにして作成します。
Create a new table

todo_items
「Insert row」 を選択し、2つほどデータを追加しましょう。
Insert row

4. Supabaseでバックエンドを構築

supabase-jsをインストール

Supabase Documentationを開きます。スクロールすると「Client Libraries」というセクションがあるので、JavaScriptを選択して閲覧しましょう。
Supabase Documentation

インストールコマンドをコピーしてターミナルで実行しましょう。
Installing

Terminal
npm install @supabase/supabase-js

Supabaseクライアントの実装

プロジェクトディレクトリ直下に utils ディレクトリを作成します。この中に、これから作成するファイルを格納していきましょう。

Terminal
mkdir utils

まずプロジェクトディレクトリ上で以下のコマンドを実行し、utilsディレクトリ直下に supabase.ts ファイルを作成します。

Terminal (mac OS)
touch utils/supabase.ts

or

Terminal (windows OS)
ni utils/supabase.ts

そして、Supabase DOCS > JavaScript Client Library > Initializing を参考に以下のコードを記述します。

supabase.ts
import { createClient } from '@supabase/supabase-js';

// Create a single supabase client for interacting with your database
export const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_ANON_KEY!
);

データのCRUDを実装

プロジェクトディレクトリ上で以下のコマンドを実行し、utilsディレクトリ直下に supabaseFunction.ts ファイルを作成します。ここに関数を記述していきます。

Terminal (mac OS)
touch utils/supabaseFunction.ts

or

Terminal (windows OS)
ni utils/supabaseFunction.ts

Supabase DOCS > JavaScript Client Library > Fetch data ~ Update data を参考に以下のコードを記述します。本来は1つずつ実装するのが良いですが、説明の簡略化のためにまとめて記述してしまいます。

supabaseFunctions.ts
import { supabase } from './supabase';

export const fetchTodoList = async () => {
  const todoItems = await supabase.from('todo_items').select('*');
  return todoItems.data;
};

export const addTodoItem = async (title: string) => {
  await supabase.from('todo_items').insert({ title: title });
};

export const deleteTodoItem = async (id: number) => {
  await supabase.from('todo_items').delete().eq('id', id);
};

export const checkTodoItem = async (id: number, status: boolean) => {
  await supabase.from('todo_items').update({ status: !status }).eq('id', id);
};

つづいて、TodoAppコンポーネントにsupabaseFunctions.tsに記述した関数を呼び出し、CRUDを実行したデータをstateに渡す処理を記述していきます。

TodoApp.tsx
// import文省略

// supabaseFunctionsの呼び出し
import {
  fetchTodoList,
  addTodoItem,
  deleteTodoItem,
  checkTodoItem,
} from '../../utils/supabaseFunctions';

// JSONサーバーの記述を削除
// const API_URL = 'http://localhost:3000/todo/';

interface Todo {
  id: number;
  title: string;
  status: boolean;
}

const TodoApp = () => {
  const [todoList, setTodoList] = useState<Todo[]>([]);

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

  // const fetchTodo = () => {
  //   fetch(API_URL)
  //     .then((responseData) => {
  //       return responseData.json();
  //     })
  //     .then((result) => {
  //       setTodoList(result);
  //     });
  // };

  // SupabaseによるfetchTodo関数を定義
  const fetchTodo = async () => {
    const todoList = (await fetchTodoList()) as Todo[];
    setTodoList(todoList);
  };

  // const addTodo = (inputTitle: string) => {
  //   const addData = {
  //     title: inputTitle,
  //     status: false,
  //   };
  //   fetch(API_URL, {
  //     body: JSON.stringify(addData),
  //     method: 'POST',
  //     headers: {
  //       'Content-Type': 'application/json',
  //     },
  //   }).then(fetchTodo);
  // };

  // SupabaseによるaddTodo関数を定義
  const addTodo = async (inputTitle: string) => {
    if (!inputTitle) return;

    await addTodoItem(inputTitle);
    fetchTodo();
  };

  // const deleteTodo = (id: number) => {
  //   const targetUrl = API_URL + id;
  //   fetch(targetUrl, {
  //     method: 'DELETE',
  //   }).then(fetchTodo);
  // };

  // SupabaseによるdeleteTodo関数を定義
  const deleteTodo = async (id: number) => {
    await deleteTodoItem(id);
    fetchTodo();
  };

  // const checkTodo = (id: number, title: string, status: boolean) => {
  //   const targetUrl = API_URL + id;
  //   const editData = {
  //     id: id,
  //     title: title,
  //     status: !status,
  //   };
  //   fetch(targetUrl, {
  //     body: JSON.stringify(editData),
  //     method: 'PUT',
  //     headers: {
  //       'Content-Type': 'application/json',
  //     },
  //   }).then(fetchTodo);
  // };

  // SupabaseによるcheckTodo関数を定義
  const checkTodo = async (id: number, status: boolean) => {
    await checkTodoItem(id, status);
    fetchTodo();
  };

  return (
    // 記述省略
  );
};

export default TodoApp;

なんとこれで完成です。ここではまとめて記述してもらいましたが、Supabase DOCSをもとに自分で考えながら実装すると力がつきます。

5. Vercelでデプロイ

GitHubからプロジェクトをインポート

Let's build something new.

ビルド設定を変更

VercelはデフォルトではNext.jsのビルド設定になっているため、以下のように変更します。
Build Command: "npm run start"
Output Directory: "dist"
Configure Project

.envファイルからEnvironment Variablesへの転記

Environment Variablesを選択して、環境変数を設定します。
.envで設定したREACT_PUBLIC_SUPABASE_URLREACT_PUBLIC_SUPABASE_API_KEYを設定しましょう。
Environment Variables

さぁ、あとは 「Deploy」 ボタンを押すだけです!

準備完了!

デプロイしたURLを開き、動作を確認してみましょう。反映されていたらこれで完成です!

総括

おそらく、最後まで走り切ってしまえば今後のフルスタックアプリケーション開発は圧倒的に楽ができ、フロントエンド開発に注力することができるでしょう。お財布事情にも優しく、非エンジニアの方に簡単に自分の成果物を見せるのにも最適です。
ぜひ皆さん、React × SupabaseプロジェクトをVercelでデプロイする方法を身に着けて自作のアプリをどんどん公開していきましょう!

参考文献

Discussion