Nuxt 3 では、アプリケーションをビルドしたあとに npm run build && npm run preview(もしくは nuxt preview)で “preview” 環境を立ち上げ、動作を確認することができます。
しかしこの「preview」環境で、$fetch または useFetch を使った API 通信が「反応しない」「エラーになる」ケースが報告されています。
この記事では、特に「nitro preview 実行時」に発生しやすい問題を深掘りし、原因と実務的な解決策を整理します。
問題の概要
- 開発モード(
npm run dev)では正常に動いていたuseFetch('/api/xxx')が、ビルド後のpreviewモードで動かない。 useFetchやuseAsyncDataを使っても、データが取得できずnullまたはエラーになる。$fetchと比較して、useFetchのほうが失敗しやすい場面がある。- 特に、内部 API(
/api/配下)への呼び出しで発生することが多い。
このような状況は「preview =本番近似環境」で動作させようとしたときに発覚するため、実運用前のチェックポイントとして重要です。
なぜ “preview” 環境で動かなくなるのか
Nitro サーバ/preview モードの構成が dev モードと異なる
開発モードでは、Vite 開発サーバと Nitro サーバが同一プロセスまたは密接に連携しており、/api/xxx のような内部ハンドラに対しては “ネットワークを跨がない呼び出し” が行われることがあります。
一方で、preview モード(nitro preview)では通常、次のような構成に近くなります:
ブラウザ → Nitro HTTP サーバ → API ハンドラ
このとき、内部 API 呼び出しが HTTP 経由で自コンテナ/プロセスにアクセスされる形になるため、開発モードと比べて以下のようなミス・制約が表面化します:
- 相対パス(
/api/...)だけでアクセスすると、ホスト名/ポート/プロトコルの解決が開発時と異なる。 - Docker/コンテナ環境やリバースプロキシ構成だと、
localhost/127.0.0.1が正しいホストではなかったり、Netlify/Vercel などのサーバレス環境では自己リクエストが制限されていたりします。 useFetch(またはuseAsyncData)が SSR(サーバサイド)コンテキストで実行されると、ブラウザと異なる環境からの呼び出しになるため、クロスオリジン/ホスト名/リダイレクトの問題が顕在化します。実際、Stack Overflow の報告でも「refresh/直アクセス時に SSR で失敗、クライアント側では成功した」というケースが確認されています。 Stack Overflow+1
$fetch と useFetch の挙動差
$fetchは比較的 “汎用 HTTP クライアント” として動作し、クライアント/サーバ両方から使えます。useFetchは Nuxt の高レベル構成要素で、SSR・クライアント両方でデータ取得および差分更新(reactivity)を担うため、サーバ側の実行時点(プロセス、環境変数、ホスト解決など)に強く依存します。
そのため、preview 環境でuseFetchが失敗しても$fetchは動いたという報告も多く、「なぜuseFetchは失敗するのか?」の理解がポイントになります。 Reddit+1
実務的な解決策
解決策 1:ランタイム設定(runtimeConfig)でベースURLを明示
環境ごとに API のベース URL を切り替えられるようにし、preview 環境でも正しいホスト/プロトコルでアクセスできるようにします。
// nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
public: {
apiBase: process.env.NUXT_PUBLIC_API_BASE || 'http://localhost:3000'
}
}
})
// 任意の composable やページ
const config = useRuntimeConfig()
const { data, error } = await useFetch(`${config.public.apiBase}/api/todos`)
本番・preview 環境では NUXT_PUBLIC_API_BASE を https://yourdomain.com などに設定しておけば、相対パスだけに頼って起きる “アクセス先ホストの誤り” を防げます。
解決策 2:Nitro の設定を確認/修正
preview モードでは nitro.config.ts や nuxt.config.ts の routeRules、proxy/devProxy が関係してきます。たとえば:
// nitro.config.ts
export default defineNitroConfig({
devProxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true
}
}
})
あるいは:
export default defineNuxtConfig({
routeRules: {
'/api/**': { cors: true, proxy: 'http://localhost:3000/api/**' }
}
})
このように、preview や本番環境で “自己呼び出し” が正しくルーティングされるかをチェックします。API が別コンテナにあったりプロキシが必要なときは特に重要です。
解決策 3:相対パスではなく絶対URLを使う/内部関数呼び出しに変更
相対パスだけだとホスト解決があいまいになりがちです。以下のいずれかを検討してください:
useFetch(${config.public.apiBase}/api/…)のように絶対 URL を使う。- サーバサイド実行(API ハンドラ内)では
$fetchを使うのではなく、直接内部関数をimportして呼び出す(自己リクエストを避ける)。たとえば:
// server/api/tasks.ts
export default defineEventHandler(() => { /* … */ })
// page or composable
import { getTasks } from '~/server/api/tasks'
const tasks = await getTasks()
こうすることで SSR 時の “HTTP 経由” ではなく、直接関数呼び出しとなり、環境差異を減らせます。
解決策 4:Docker/コンテナ環境・ホスト名の確認
Docker やクラウド環境(Netlify Edge、Vercel 等)では、localhost が適切なホスト名でない場合があります。Stack Overflow では「localhost を 127.0.0.1 に変更して動いた」という投稿もあります。 Stack Overflow
ポートフォワーディング、コンテナ名、プロキシ設定などを見直してください。
まとめ
Nuxt 3 の nitro preview 環境で fetch が動かない原因は、主に「開発モードと異なる実行コンテキスト」「ホスト/プロトコル/HTTP リクエスト構造が変わる」ことにあります。
特に useFetch を用いた SSR 領域でのデータ取得では、環境変数・ベースURL・プロキシ設定を理解しておくことで多くのトラブルを防げます。
ポイント整理
- preview 環境では HTTP 経由で API が呼ばれる構成となることが多い。
useFetchは SSR 実行時の環境に依存して失敗しやすい。runtimeConfigでベースURLを環境に応じて切り替える。- Nitro のプロキシ/routeRules を確認して内部APIのルーティングを確保。
- コンテナ・クラウド環境ではホスト名(
localhostvs127.0.0.1)にも注意。
これらを押さえておくことで、preview 環境だけでなく本番運用でも同様の通信エラーを未然に防ぐ設計が可能です。


