EY-Office ブログ

React Server Componentを本番環境で動かしてみたら、少しハマった

従来のクライアントだけで動くReactアプリはビルドされたJavaScript/HTML/CSSファイルをWebサーバー上にデプロイすれば動きましたが、React Server ComponentではServer Componentを実行する環境をサーバー上で動かす必要があります。

今まで、このブログではRSC(React Server Component)を開発環境で動かしてきましたが、開発環境では(例えば)Next.jsがPC/Mac等の上で動いているので問題無くServer Componentも実行できていました。

deploy-RSC-to-production Bing Image Creatorが生成した画像を使っています

今回の本番環境

今回動かすのは、いつものジャンケンアプリです。このブログで説明したコードを整理したものを、Next.js(React)は最新の安定版で動かしています(Next.jsは14.2.3、Reactは18.3.1です)。

  • アプリの実行環境はNext.jsで動きます
  • JavaScriptの実行環境が必要なのでNode.jsが必要ですね
  • Next.jsの前段にはReverse proxyとしてnginxを入れます
    • 今回はサブドメインreact.xxxx.yyyからNext.jsのlocalhost:3030への変換を行っています
    • またLet’s Encryptを使ってhttpsのサポートをしています
  • データベースはMySQLを作っています

今回起きた問題たち

今までに行った事がないことを試すと、必ずトラブルが発生します。😅

1. Node.js v20がインストールしようとしたら

Next.jsのSystem RequirementsにはNode.js v18.17以上が必要と書いてあります。サーバーにはnvmがインストールされているので、v20.13.1(現在のTLS)をインストールしようとしたのですが、以下のエラーが発生しインストールできませんでした。

node: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.28' not found (required by node)

実はサーバーはUbuntu v18だったのです、諦めUbuntu v20にアップデートしました。これで無事にNode.js v20がインストール出来ました。

2. アプリをDeploy起動

アプリのデプロイは以下の手順でデプロイを行いました。ブラウザーからアクセスしたところ問題無くうごきました。

$ git clone git@github.com:XXXXX/next-rsc-jyanken2.git
$ cd next-rsc-jyanken2
$ npm install
$ npx migrate deploy
$ npm run dev -p 3030

その後、Next.jsのDeploy - Self-Hosting - Node.js Serverにあるように、

$ npx migrate deploy
$ npm run build
$ npm start -p 3030

で無事に本番サーバーが動作しました。

3. Reverse proxy nginxを設定

nginxは他のサーバーで動くアプリでも使っているので設定ファイルを追加し、サブドメインreact.xxxx.yyyで動くように設定したところ、https://react.xxxx.yyy でジャンケン画面は表示されましたが、ジャンケン・ボタンを押しても反応がありません! Next.jsのログを見ると以下のようなエラー発せしていました。

`x-forwarded-host` header with value `localhost:3030` does not match `origin` header with value `react.xxxx.yyy` from a forwarded Server Actions request. Aborting the action.
Error: Invalid Server Actions request.
    at rP (/home/yuumi3/works/next-rsc-jyanken2/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:15:7099)

  ・・・省略・・・

    at nk.render (/home/yuumi3/works/next-rsc-jyanken2/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:19:4540) {
  digest: '2710703285'
}

検索したところこのページに解答がありました、next.config.mjsファイルにallowedOriginsを設定するれば良いようです(ひょっとしたらnginxの設定でも可能かもしれません?)。
next.config.mjsファイルを変更したところ無事にジャンケン・ボタンも動作するようになりました。

  • next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    serverActions: {
      allowedOrigins: ["react.xxxx.yyy", "localhost:3030"]
    }
  }
};

export default nextConfig;
  • nginxの設定

注: 本番運用用には適切ではないかも知れませんので、参考程度に見てください。

server {
  listen 80;
  server_name   react.xxxx.yyy;
  server_tokens off;

  return 301 https://$host$request_uri;
}

server {
  listen        443 ssl;
  server_name   react.xxxx.yyy;
  server_tokens off;

  ssl_certificate       /etc/letsencrypt/live/react.xxxx.yyy/fullchain.pem;
  ssl_certificate_key   /etc/letsencrypt/live/react.xxxx.yyy/privkey.pem;
  ssl_protocols TLSv1.3 TLSv1.2;

  location / {
    proxy_pass http://localhost:3030;
    proxy_redirect off;
  }
}

4. データベースをMySQLに変更

ここまでアプリはsqlite3で動いていました、実はPrismaを本気で使った事がなく、 ここで苦労しました。😅

4a. Prismaは開発環境と本番環境で異なる種類のRDBは使えないのかも

この部分は、十分に調べきれてないので憶測も含むので注意してください。

RDBをMySQLに切り替えるには、schema.prismaファイルのdatasource dbを以下のようにproviderをmysqlにし、接続情報をurlに指定します。以下では環境変数DATABASE_URLから取得しています。

datasource db {
  provider          = "mysql"
  url               = env("DATABASE_URL")
}

datasource dbを書き変え、このアプリ用のデータベース、アカウント・権限等をMySQLに作成してnpx prisma migrate deployしましたが、エラーになってしまいました。
たしかに、prisma/migrations/migration_lock.tomlファイルにはprovider = "sqlite"と書かれています。これをmysqlに変更してもマイグレーションファイル内のSQLが文法エラーYou have an error in your SQL syntaになっていました、sqlite3依存のSQL文のようです。

ここで既存のマイグレーションを削除し、新たにマイグレーションを開始しましたが、P3014エラーが発生してしまいました。

4b. prismaのshadow databasとは

エラー原因を調べる段階でPrismaについて色々と学びました。PrismaのマイグレーションはRuby on Railsのマイグレーションより高能力で、Alter Table SQL文では出来ないような変更も行えます。
このような高度な変更を行う際に、Prismaはshadow databaseという一時的なデータベースを作り、そこにデータを一時的に待避し、テーブル変更後にデータを読み込んだりしています。

そのためにMySQLのアカウントにはShadow database user permissionsに書かれているように、CREATE, ALTER, DROP, REFERENCES ON *.* 権限が要ると書かれています。しかし、この強力な権限を与えるのには心理的に抵抗があります(このMySQLは他アプリでも使っているので)。

さらに調べていたらCloud-hosted shadow databases must be created manuallyを見つけました。schema.prismaファイルのdatasource dbshadow databaseを指定できるようです。早速MySQLにshadow database用のデータベースを作成し設定したところマイグレーションが成功しました。

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

datasource db {
  provider          = "mysql"
  url               = env("DATABASE_URL")
  shadowDatabaseUrl = env("SHADOW_DATABASE_URL")
}

model Scores {
  id        Int      @id @default(autoincrement())
  human     Int
  computer  Int
  judgment  Int
}
  • MySQLのデータベース、ユーザー、権限作成
CREATE DATABASE react CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;
CREATE DATABASE react_shadow CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;

CREATE USER ‘react’@‘localhost' IDENTIFIED BY ‘react’;
GRANT ALL PRIVILEGES ON react.* TO 'react'@'localhost';
GRANT ALL PRIVILEGES ON react_shadow.* TO 'react'@'localhost';

まとめ

React Server Component(RSC)自体を番環境で動かすこと自体は難しくない事が判りました。ただし本番環境でRSCを運用するには、いろいろな技術を正しく知る必要がありますね。

今後を考えるとPrismaを、もう少し学ばなくてはと思いました。😃

- about -

EY-Office代表取締役
・プログラマー
吉田裕美の
開発者向けブログ