【Next.js】【エラー】TypeError: fetch failed
Vercel に Next.js をデプロイしたときに「TypeError: fetch failed」がでて
デプロイに失敗しました。
解決していきまーーーす!
TypeError: fetch failed ①
エラー内容確認
Vercel でデプロイログを確認しました
TypeError: fetch failed
at Object.fetch (node:internal/deps/undici/undici:11576:11)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
cause: Error: connect ECONNREFUSED 127.0.0.1:3000
at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1495:16)
at TCPConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) {
errno: -111,
code: 'ECONNREFUSED',
syscall: 'connect',
address: '127.0.0.1',
port: 3000
}
}
Error occurred prerendering page "/". Read more: https://nextjs.org/docs/messages/prerender-error
TypeError: fetch failed
at Object.fetch (node:internal/deps/undici/undici:11576:11)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
fetch で Route Handlers を呼び出したときにエラーになってるっぽい
ググってみる
「TypeError: fetch failed」「ECONNREFUSED」ここら辺の単語でググってみました
↑ の記事によると、
Node.js 17/18 では localhost が IPv6 で名前解決されることが原因みたいです。
Vercel で Project Settings > General > Node.js Versionを確認してみると、
確かに 18 系っぽい。。
ただ、デプロイログを再度見てみると 127.0.0.1 で解決できているので原因は別にありそう
まあ、一応試してみます。
コード修正
たたいている API の URL をlocalhost ⇒ 127.0.0.1 へ変更し IPv4 を直書きしてみます
// 変更前
const url = "http://localhost:3000/api/user";
↓
// 変更後
const url = "http://127.0.0.1:3000/api/user";
再デプロイ
修正したコードを GitHub に push して再デプロイしてみます。
失敗しました。。。
デプロイログを見てみると、エラーの内容変わらずでした。
TypeError: fetch failed
at Object.fetch (node:internal/deps/undici/undici:11576:11)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
cause: Error: connect ECONNREFUSED 127.0.0.1:3000
at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1495:16)
at TCPConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) {
errno: -111,
code: 'ECONNREFUSED',
syscall: 'connect',
address: '127.0.0.1',
port: 3000
}
}
うーーん、なんでしょう??
TypeError: fetch failed ②
エラー内容確認
fetch failed なので、やっぱり API がたたけないことが原因ですよね。。
localhost とか 127.0.0.1 ではなく、Vercel のデプロイ用の URL じゃないとダメなのか??
試してみます
Vercel のデプロイ用の URL とは?
Vercel でデプロイしたときに発行される URLのこと。Git でいうコミット ID ぐらいの感覚。
この値は Vercel で設定されているVERCEL_URLという環境変数から取得できます
Vercel で設定されている環境変数については ↓ をご覧ください
今回はClient Component でもこの環境変数を使用したいので、
NEXT_PUBLIC_VERCEL_URLを使います
コード修正
API の URL を環境変数から生成するようにします
(変更前)
// APIのURL
const url = "http://localhost:3000/api/user";
// APIへリクエスト
const res = await fetch(url, {
cache: "no-store",
});
(変更後)
- .env ファイルに NEXT_PUBLIC_API_PREFIX と NEXT_PUBLIC_VERCEL_URL を定義
- 環境変数を読み込む src/lib/config.ts を作成
- API の URL を config.ts ファイルを読み込み生成
という 3 つのステップで修正します。
1..env ファイルに NEXT_PUBLIC_API_PREFIX と NEXT_PUBLIC_VERCEL_URL を定義
NEXT_PUBLIC_API_PREFIX="http://"
NEXT_PUBLIC_VERCEL_URL="localhost:3000"
NEXT_PUBLIC_VERCEL_URL では、https:// が取得できないので、NEXT_PUBLIC_API_PREFIX として定義しておきます。
2.環境変数を読み込む src/lib/config.ts を作成
export const config = {
apiPrefix: process.env.NEXT_PUBLIC_API_PREFIX ?? "http://",
apiHost: process.env.NEXT_PUBLIC_VERCEL_URL ?? "localhost:3000",
};
3.API の URL を config.ts ファイルを読み込み生成
import { config } from "@/lib/config";
// APIのURL
const url = config.apiPrefix + config.apiHost + "/api/user";
// APIへリクエスト
const res = await fetch(url, {
cache: "no-store",
});
環境変数から API の URL を指定すると、undefined 同士の足し算でエラーになります
なので、config.ts でデフォルト値を指定し string 型にして足しています
ビルド設定修正
NEXT_PUBLIC_VERCEL_URL は Vercel で設定されているのですが
NEXT_PUBLIC_API_PREFIX はあらかじめ設定しておく必要があります
Project Settings > Environment Variablesにて
Key :NEXT_PUBLIC_API_PREFIX
Value :https://
で環境変数を設定します
再デプロイ
修正したコードを GitHub に push して再デプロイしてみます。
失敗しました。。。
デプロイログを見てみると、エラーの内容変わりました。
SyntaxError: Unexpected token < in JSON at position 0
at JSON.parse (<anonymous>)
at parseJSONFromBytes (node:internal/deps/undici/undici:6662:19)
at successSteps (node:internal/deps/undici/undici:6636:27)
at node:internal/deps/undici/undici:1236:60
at node:internal/process/task_queues:140:7
at AsyncResource.runInAsyncScope (node:async_hooks:203:9)
at AsyncResource.runMicrotask (node:internal/process/task_queues:137:8)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
SyntaxError: Unexpected token < in JSON at position 0
エラー内容確認
SyntaxError: Unexpected token < in JSON at position 0
at JSON.parse (<anonymous>)
at parseJSONFromBytes (node:internal/deps/undici/undici:6662:19)
at successSteps (node:internal/deps/undici/undici:6636:27)
at node:internal/deps/undici/undici:1236:60
at node:internal/process/task_queues:140:7
at AsyncResource.runInAsyncScope (node:async_hooks:203:9)
at AsyncResource.runMicrotask (node:internal/process/task_queues:137:8)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
「JSON だと思ったけど1文字目が<だから、JSON じゃなくない??」
というエラーです。
1文字目が<のため、おそらく API から HTML が返ってきていると思います。
API からのレスポンスヘッダーを確認する
↓ のようにヘッダを出力させます
import { config } from "@/lib/config";
// APIのURL
const url = config.apiPrefix + config.apiHost + "/api/user";
// APIへリクエスト
const res = await fetch(url, {
cache: "no-store",
});
// レスポンスヘッダを出力する
console.log(res.headers);
// レスポンスボディを取り出す
const data = await res.json();
このコードでデプロイしてみるとログがこんな感じでした
HeadersList {
cookies: null,
[Symbol(headers map)]: Map(23) {
'cache-control' => {
name: 'Cache-Control',
value: 'public, max-age=0, must-revalidate'
},
'connection' => { name: 'Connection', value: 'keep-alive' },
'content-encoding' => { name: 'Content-Encoding', value: 'br' },
'content-security-policy' => {
name: 'Content-Security-Policy',
value: "default-src 'self' vercel.com *.vercel.com vercel.live instant-preview-site.vercel.app instant-preview-site-bc8byjwcq.vercel.sh;script-src 'self' 'unsafe-eval' 'unsafe-inline' va.vercel-scripts.com vercel.com *.vercel.com vercel.live instant-preview-site.vercel.app instant-preview-site-bc8byjwcq.vercel.sh;style-src 'self' 'unsafe-inline' vercel.com *.vercel.com vercel.live instant-preview-site.vercel.app instant-preview-site-bc8byjwcq.vercel.sh;font-src 'self' vercel.com *.vercel.com vercel.live instant-preview-site.vercel.app instant-preview-site-bc8byjwcq.vercel.sh *.gstatic.com;connect-src data: *;"
},
'content-type' => { name: 'Content-Type', value: 'text/html; charset=utf-8' },
'date' => { name: 'Date', value: 'Sat, 16 Sep 2023 14:56:46 GMT' },
'feature-policy' => {
name: 'Feature-Policy',
value: "fullscreen 'self'; camera 'none'"
},
'referrer-policy' => { name: 'Referrer-Policy', value: 'origin-when-cross-origin' },
'server' => { name: 'Server', value: 'Vercel' },
'strict-transport-security' => {
name: 'Strict-Transport-Security',
value: 'max-age=31536000; includeSubDomains; preload'
},
'vary' => {
name: 'Vary',
value: 'RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Url'
},
'x-content-type-options' => { name: 'X-Content-Type-Options', value: 'nosniff' },
'x-dns-prefetch-control' => { name: 'X-Dns-Prefetch-Control', value: 'on' },
'x-download-options' => { name: 'X-Download-Options', value: 'noopen' },
'x-edge-runtime' => { name: 'X-Edge-Runtime', value: '1' },
'x-frame-options' => { name: 'X-Frame-Options', value: 'DENY' },
'x-matched-path' => { name: 'X-Matched-Path', value: '/[[...slug]]' },
'x-powered-by' => { name: 'X-Powered-By', value: 'Next.js' },
'x-robots-tag' => { name: 'X-Robots-Tag', value: 'noindex' },
'x-vercel-cache' => { name: 'X-Vercel-Cache', value: 'MISS' },
'x-vercel-id' => {
name: 'X-Vercel-Id',
value: 'iad1:iad1:iad1::7zktw-1694876206302-13dbeb12bf20'
},
'x-xss-protection' => { name: 'X-Xss-Protection', value: '0' },
'transfer-encoding' => { name: 'Transfer-Encoding', value: 'chunked' }
},
[Symbol(headers map sorted)]: null
}
'content-type' => { name: 'Content-Type', value: 'text/html; charset=utf-8' },
なので、やっぱり HTML が返ってきていますね。
コード修正
どうして HTMLが返ってきているのかは不明だったので、
レスポンスボディを取り出す処理を try-catch していきます
(詳しい方いたら、おしえてください!!)
import { config } from "@/lib/config";
// APIのURL
const url = config.apiPrefix + config.apiHost + "/api/user";
// APIへリクエスト
const res = await fetch(url, {
cache: "no-store",
});
// レスポンスボディを取り出す
try {
const data = await res.json();
} catch(error) {
condsole.log(error)
}
try-catch ではなく、Client Component に変更するのもありだと思います
再デプロイ
修正したコードを GitHub に push して再デプロイしてみます。
成功しました!!
最後に
今回はとりあえず try-catch で逃げましたが、
API から HTML が返ってきている原因を探らないとですね。。
Discussion