Nuxt3のproxyについて

この記事はウェブクルー Advent Calendar 2023 18日目の記事です。

Nuxt3でproxyを実装するにあたって色々調べたことについて書いていきます。

## 前提
- nuxt 3.6.5
- node 16.14.2
- SSR

※推奨環境ではないです。
公式の出している前提はこちらです
https://nuxt.com/docs/getting-started/installation#new-project

## 結論
- composablesにuseFetchをラップするuseCustomFetchを作成しfetchの共通設定を書く。
- proxyはnitroのrouteRulesから設定する。

```typescript:composables/useCustomFetch.ts
export const useCustomFetch = async <
  T,
  OpsT = Parameters<typeof useFetch<T>>['1']  
>(
  url: string,
  options?: OpsT
) => {
  const store = useSessionStore();
  const authHeader = { Authorization: store.sessionToken };
  return await useFetch<T>(url, {
  ...options,
  onRequest({ options }) {
    if(store.sessionToken) {
      options.headers = { ...options.headers, ...authHeader }
    }
  },
}
```
```typescript:nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    routeRules: {
      '/api/**': {
        proxy: `${process.env.NUXT_API_URL}/**`,
      },
    },
  },
});
```
- useCustomFetchの呼び出し箇所では補完が効いたのですがuseCustomFetchの関数内ではoptionsのオブジェクトに何が含まれているのかが補完できなかったのでおそらくもっといい方法があるかもしれないです。

## 最初の実装
http-proxy-middlewareとserver/middleware/proxy.tsでの実装
fetchの共通設定はplugins/globalFetch.tsで設定
```typescript:server/middleware/proxy.ts
import { createProxyMiddleware } from 'http-proxy-middleware';
const { proxyTarget } = useRuntimeConfig();

const proxyMiddleware = createProxyMiddleware('/api', {
  target: proxyTarget,
  changeOrigin: true,
  ws: true,
  pathRewrite: { '^/api': '' },
});

export default fromNodeMiddleware((req, res, next) => {
  /** @ts-ignore */
  proxyMiddleware(req, res, next);
});
```
```typescript:plugins/globalFetch.ts
export default defineNuxtPlugin(() => {
  globalThis.$fetch = $fetch.create({
    noRequest({ options }) {
      /** ヘッダー追加などリクエスト時の共通設定 */
    },
  });
});
```
- 動くことは動くがcreateProxyMiddlewareとfromNodeMiddlewareの型が一致しない為ts-ignoreが必要になってしまいます。
- useFetchを実行した際にSSRの場合以下の条件にかかりglobalThis.$fetchではなくuseRequestFetchが使用されるためplugins/globalFetch.tsに実装した共通設定が適応されません。

https://github.com/nuxt/nuxt/blob/v3.6.5/packages/nuxt/src/app/composables/fetch.ts#L113-L118

- ↑の回避の為useFetchを実行する際にSSRの時のみドメインを追加する処理にしました。
- ↑useFetchのkeyは設定しない場合urlとoptionsによって自動で生成されるがurlがSSRとCSRで異なるためリクエストの重複が排除されません。そのためそれぞれのuseFetchの実装にユニークなkeyを設定し重複を排除しました。

## 2番目の実装
H3のproxyRequestでの実装
fetchの共通設定はplugins/globalFetch.tsで設定
```typescript:server/api/[...].ts
const { proxyTarget } = useRuntimeConfig();

export default defineEventHandler((event) => {
  const requestUrl = getRequestURL(event);
  const rewritePath = requestUrl.pathname.replace('/api', '');
  return proxyRequest(event, proxyTarget + rewritePath + requestUrl.search);
});
```
- http-proxy-middlewareを使用しなくなったので型の不一致によるts-ignoreがなくなりました。

## 最終的な実装
冒頭の結論と内容は重複してしまいますが
composablesにuseFetchをラップするuseCustomFetchを作成しfetchの共通設定を書く。
proxyはnitroのrouteRulesから設定する。
- useFetchの実行すべてに書いた設定が適応されるためSSRでuseFetchを実行するときに`globalThis.$fetch`とuseRequestFetchによる設定の差分がなくなりました。
- useCustomFetchの実行時にonRequestでヘッダーを追加するためsessionTokenなどのヘッダーに入れたい値などが都度入るようになりました。
- nitroのrouteRulesでは2番目の実装の時に使用していたproxyRequestが使用されておりより簡単に実装できます。また/api/だけでなく/proxy/などでも対応できるため柔軟な対応が可能です。

routeRulesの処理が実際に書かれているソースコードです。（Nuxt3.6.5ではnitropack2.5.2が使用されています。そのため最新バージョンではなくnitropack2.5.2のソースコードを載せています）

https://github.com/unjs/nitro/blob/v2.5.2/src/runtime/route-rules.ts#L35-L53

## やったこと
- vscodeでctrl + クリックでnode_modulesのファイルを追って実装の確認
- Nuxtの仕様理解の為の記事や公式ドキュメントの確認
- Nuxtとnitroのgithubでソースコードの確認

## 最後に
公式のドキュメントに記載が全然なかったり、rc版でのproxyのやり方であったりhttp-proxy-middlewareのbeta版でのやり方などいろいろな記事を見ましたが現状Nuxt3ではこれがベストなproxyの設定かなと思います。
また記事を書いている途中に気づいたのですが@nuxtjs/proxyのgithubの方にはrouteRulesを使ってproxyできることがかかれていました。もっと早くこれに気づいていればと...

https://github.com/nuxt-community/proxy-module#nuxt-3

明日は@yuko-tsutsuiさんの投稿になります。

### 参考

- https://nuxt.com/
- https://nitro.unjs.io/
- https://developer.mamezou-tech.com/nuxt/nuxt3-universal-fetch/