Vercel から Lambda 呼び出し時の CORS エラーで一生ハマった話

久しぶりです、Xu です。

この挨拶もめんどくさくなってきましたね、そろそろ月一くらいのペースで記事書けたらもう久しぶりって言うこともないかもしれません。（初めましてがほとんどか。。。）

私事ですが、今月挙式しました。
式とは言っても披露宴のみですが、まあやってよかったと思ってます。

さて表題の件ですが、皆さんも一度は見たことあるんじゃないでしょうか？

```error.log
Access to fetch at 'https://~.lambda-url.ap-northeast-1.on.aws/' from origin 
'https://~.vercel.app' has been blocked by CORS policy: Response to preflight 
request doesn't pass access control check No 'Access-Control-Allow-Origin' 
header is present on...
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2466211/62d79bd2-5da9-4bac-8222-8991e107c536.png)

割とトラウマの人も多いのでは？

## CORS とは
簡単に言うと、異なるドメインのウェブサイトやサービス間でデータや画像、スクリプトなどを共有できるようにする仕組みです。
エラーを起こすパターンとしては以下があります。
- Http からサーバーへリクエストを送信する際
- サーバー側の CORS 設定で許可されていない URL からのリクエストを送信した際
- リクエストの不完全
- レスポンスの情報が不完全のためリクエスト失敗と勘違いされている

ここで重要なのは、リクエストの行でコードのタイポなどがあった場合でも CORS エラーのように見えてしまうのが厄介！

割とこれめんどくさいですよね、当方は夜 9 時くらいから色々いじり始めて気づいたら深夜 2 時でした。。。
途中家内に怒られてお風呂は入りました、作業時間水増しですね（笑）

まずは今回どのように解決していったか振り返ってみましょう。

## 試したこと 1. mode: "no-cors" を設定
せや、エラーメッセージ `If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.` にも書いてあるし、とりあえず no-cors にしたろ！

と最初当方と同じことを思ったそこのあなた、それだとレスポンスの JSON 受け取れないので 405 エラーが発生する可能性があります！
なので、お勧めはしませんが、それでも動くようなものであれば使ってもいいかも。

## 試したこと 2. Lambda の設定を確認
Lambda の設定 > 関数 URL 画面で CORS を有効化してみた。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2466211/3d32d7b7-4ede-4e0d-a2d2-4b5286d9778e.png)

結果としてはあまり変わらなかった。
Vercel 自体使い始めてたのがつい最近なので、もしかして結構厳しいのではと思っていたが、特にそういうわけでもないらしい。

## 試したこと 3. Lambda にレスポンス headers を追加
```Python lambda_function.py
cors_headers = {
    'Access-Control-Allow-Origin': 'https://*.vercel.app',
    'Access-Control-Allow-Headers': '*',
    'Access-Control-Allow-Methods': 'GET, POST, OPTIONS'
}

method = event.get("requestContext", {}).get("http", {}).get("method", "")
  if method == "OPTIONS":
    return {
      'statusCode': 200,
      'headers': cors_headers,
      'body': 'Preflight Passed'
    }
  if method == "POST":
    try:
        ～～処理～～
    return {
        'statusCode': 200,
        'headers': cors_headers,
        'body': json.dumps(data, ensure_ascii=False)
      }

    except Exception as e:
      logger.error(f"Error Occurred:{e}\nRequest:{event}")
      ～～処理～～
      return {
        'statusCode': 500,
        'headers': cors_headers,
        'body': json.dumps(data, ensure_ascii=False)
      }
```

`if method == "OPTIONS":`
こちらについて、OPTIONS リクエストというものにあまり馴染みがない方もいるかと思います。
これは Developer Tool などで見ると、Preflight という、POST や GET が送信される前に試しに送信されるものです。

■ preflight について
https://developer.mozilla.org/ja/docs/Glossary/Preflight_request

通常特に指定しなくても概ね返されますが、場合によってはちゃんと書いてあげないとアプリ側がおバカさんになってエラーを吐くこともあります。
念のため、レスポンスに `Access-Control-Allow-Origin` をしっかり書いておきましょう。

これでどうだ？
--> まだだめです！

最初は？？？ってなったけど、よくよく見たら、Preflight の前に先に fetch がエラーを吐いてますね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2466211/b8a132a3-f189-4d9f-ba2d-da8e1eb3538a.png)

つまりそもそも Lambda に届いてないじゃないか、しかも Postman でしっかりレスポンスもらってたので直書き手動定義じゃあ意味をなしてないですね...

これはおかしいと思い、非同期処理の書き方がまずかったかまた数時間を使い...

## 解決方法
ChatGPT ちゃんにも聞いて、アプリ側のリクエストにミスがある可能性あるって念頭にあったから再度コード全体を見直した。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2466211/fa74a7e8-1ecb-4f08-9073-c384f0893fb6.png)
結構詳細に書いてくれてますよね！

そこで発見したのは、こいつ怪しくね？
`'use client';`

src > app > page.tsx に色々と直書きしていたが、TS で window 要素を操作したかったから書いてたけど、よくよく考えたらこいついたらフロント側でリクエスト送ってることになるやん？

■ use client について
https://zenn.dev/luvmini511/articles/ec0e874a2cc1f1

はっ！何をしていたんだ、ここはどこ？私は誰？今そんな状態です。

結局以下のような構造を作ってリクエスト部分を Server Component として送信できるようにしました。
```
src
|
app---
|     |
api  page.tsx
|
search 
|
route.ts
```
変更前
```TypeScript page.tsx
const res = await fetch(process.env.NEXT_PUBLIC_LAMBDA_URL as string,
      {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "x-forwarded-proto": "https"
        },
```
変更後
```TypeScript page.tsx
const res = await fetch('/api/search',
      {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "x-forwarded-proto": "https"
        },
```

```TypeScript route.ts
export async function POST(req: Request) {
    const { data } = await req.json();
  
    const response = await fetch(process.env.LAMBDA_URL as string, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
  ```
これで無事リクエストが届くようになりました！


## あとがき
ったく手こずらせやがって、殴り書きのメモで軽く発狂してました💦
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2466211/4b1c0f7c-7892-456e-a58e-e4e46cd5925b.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2466211/1258b94f-51ba-47dd-8a71-3cb52d459af6.png)

CORS エラーって言っても可能性が多すぎるから対処するのも大変ですよね。
困ってる誰かの助けになれればと思いこの記事を書きました。
ぜひ参考になれれば幸いです。