Vite で別のサーバーと一緒に動かしたいときは server.proxy オプションを使うメモ

Vite で別のサーバーと一緒に動かしたいときは server.proxy オプションを使うメモです。

背景

ちょっとまだうまく図表を交えて分かりやすくはできてなくて、いったん文章でツラツラ書きます。

Vite で WebXR などでフロントエンドを組んでいくと、いずれ出てくるのがクロスドメイン制約(CORS) によって他のドメインのサーバーと直接はやりとりできなくて困ることです。

ならば間接的に。と、これをうまく越えるための同じドメインで中継してくれるサーバー(中継サーバー)を作ります。

フロントエンドからは中継サーバー API にアクセスすれば同じドメインだから問題なく動作しますし、その中継サーバーから他のドメインへアクセスすればバックエンドでのやりとりなのでうまくデータが取得できますし、さらに中継サーバーからフロントエンドにデータを返答すれば、フロントエンドでうまくデータが取得できます。いいね!

しかし、Vite を使っていると(自分にとって)困る点があります。

もちろん、本番環境であれば npm run build 後の環境で Express なりで作ったサーバーをベースにして立ち上げてしまえばいいんですが、ホットリロードなど Vite サーバーが効いている開発環境の場合は悩ましいんです。

Vite サーバーがメインに立ってしまうと本番で使う Express サーバーは当然別で(裏で)立ち上げる形になってしまい、開発環境のときだけ URL を変えるような手段を取らないといけません。

ただ、このやりかた、弱点があります。

GitHub Codespases でのサーバー起動だと localhost のポート違いの動作ではなくて、ポートごとにサブドメインが起動して、これまたパスが変わってしまうので、開発だと別サーバーへ、本番だと同じドメイン配下へと振り分ける必要があります。これでも、今回は GitHub Codespases だけ想定!とか今回はローカルだけ想定!とかすれば、一人であればまあまあ開発できます。おおむね解決なので、小さい開発ならゴリ押すこともできます。
しかし、それだと共同作業するときが他の人に負荷をかけます。たとえば GitHub Codespases 環境であれば起動したサーバーごとに URL みて、いちいちパスを変えてください、というような運用になってしまい共有しやすさが下がるところがあります。うーん。

と、周辺事情はまだいくらかあるんですが、これの結論としては「Vite サーバーの一部パスのときだけ、別で起動しているサーバーに転送(プロキシ)しよう」ということで解決できることがわかりました。

背景が長くなりましたね。とはいえ、こう、薄く長く悩んでいたことなので、自分の思考の経緯をここに留めさせていただきました。

ということで、まず Vite 用意

VanillaJS で TypeScript な環境を作っておいたとします。

vite-server-proxy-option-simple_07.png

npm run dev を立ち上げると、

vite-server-proxy-option-simple_01.png

こんな感じでシンプルに動く状況。今回はフロントエンド側はルートで表示されることが確認できればよいです。

この後、Vite サーバーに設定を加えるので npm run dev でのサーバー起動はいったん終了します。

Vite のフォルダで別サーバーをつくる

今回で言う中継サーバーを Express で簡易的に作ります。

npm i express

で Express をインストールします。

vite-server-proxy-option-simple_09.png

Vite 内は ES module 指定になっているので Vite 内など ES Module 指定のフォルダ内で __dirname を通すメモ あたりに気をつけつつ server.js でつくります。

プログラムは以下の通りです。/api/path1 と /api/path1/path2 のパスに GET リクエストすると API の返答が来る仕組みです。

import express from 'express'
import path from 'path';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const app = express();

app.use(express.static(__dirname + '/public'));

// bodyParser
app.use(express.json())
app.use(express.urlencoded({ extended: true }));

app.get('/api/path1', (req, res) => {
  res.json({"result":"/api/path1 GET OK!"})
});

app.get('/api/path1/path2', (req, res) => {
  res.json({"result":"/api/path1/path2 GET OK!"})
});

app.listen(process.env.PORT || 8080, () => {
  console.log("server start!");
  console.log(`app listening at http://localhost:${process.env.PORT || 8080}`)
})

ではサーバー起動します。

node server.js

別のターミナルウィンドウを表示して、このコマンドで起動します。

サーバーが起動したら /api/path1 と /api/path1/path2 がちゃんと動いている確認しておきます。

これをうまく Vite 開発サーバーと合体させましょう。

server.proxy オプションを使う

Vite サーバーの一部パスのときだけ、別で起動しているサーバーに転送(プロキシ)するような仕組み server.proxy オプションを使います。

vite-server-proxy-option-simple_02.png

Server Options | Vite

公式ドキュメントでは色々な指定の仕方が書いてあってとても勉強になります。

とはいえ、今回はシンプルに Vite サーバーで /api 以下は、さきほど起動した別サーバーの localhost:8080 の /api に転送する仕組みです。

vite-server-proxy-option-simple_03.png

まだ vite.config.js がないので新規に作って以下のような設定を書いて保存します。

import { defineConfig } from 'vite'

export default defineConfig({
    server: {
      proxy: {
        '/api': 'http://localhost:8080/',
      },
    },
  })

もちろん、もし vite.config.js で色々な設定をしている場合は、追加するような書き方をしましょう。

起動してみる

ということで、再度 npm run dev でサーバーを起動します。

vite-server-proxy-option-simple_04.png

今回は GitHub Codespaces で対応してます。うまくフロントエンドはルートで起動してます。

vite-server-proxy-option-simple_05.png

/api/path1 にアクセスすると、ちゃんと転送されてます!

vite-server-proxy-option-simple_08.png

/api/path1/path2 もアクセスすると、こちらもちゃんと転送されてます!

vite-server-proxy-option-simple_06.png

/api/path3 というサーバー側で存在しない API パスはちゃんとサーバーエラーになってます。

vite-server-proxy-option-simple_00.png

/123 のような /api は以下ではなくフロントエンドの遷移しそうなパス指定の場合は Vite サーバ側が反応してます。

ということで、Vite で別のサーバーと一緒に動かしたいときは server.proxy オプションでうまく動かせたのでよかったです。

自分の調べ方が悪いときは公式ドキュメントの中で SSR 的なサーバー作成手法をひっかけてしまったり、Express にミドルウェア敵に入れて Vite を内包するような手法を見つけてしまい、シンプルにアプローチできないときがあるので、このアプローチをまとめられてよかったです。