Nuxt 4「404を即時リダイレクト」——Nitroエラーフック実装の読み解きとSEO・運用の落とし穴

Nuxt 4「404を即時リダイレクト」——Nitroエラーフック実装の読み解きとSEO・運用の落とし穴

エンジニアブログ
最終更新日:2025年08月24日公開日:2025年08月16日
柳澤 大志
writer:柳澤 大志
XThreads

Nuxt 4(サーバー層は「Nitro」、HTTPユーティリティは「H3」)では、サーバープラグインからランタイムのエラーフックに介入してレスポンスを書き換えることができます。

今回は「404が起きたらトップへ302でリダイレクトする」実装を例に、コードの意図、プロダクションでの注意点、SEOへの影響、そして代替手段との比較までを一気に整理します。

結論から言えば、サーバー側での即時リダイレクトは強力ですが、使いどころを誤ると「ソフト404」などの副作用を招くため、要件に応じた設計判断が不可欠です。

本記事で紹介する実装例では404ページの恒久的なリダイレクト実装を、Nuxtの/server/plugins自動登録およびNitroのライフサイクルで組み込む際のリスク・落とし穴を丁寧に解説します。

尚、パブリック公開するアプリ開発においてはGoogle検索エンジンに優しい404ハンドリングを意識しなけばいけませんが、SPA開発時はまぁ無視しても良いかなとは感じます。それらの前提も踏まえ、ご覧いただけましたらと思います。

引用:Server Plugins - Nuxt Directory Structure v4
Nuxtは~/server/plugins内のファイルを自動でNitroプラグインとして登録し、Nitroのライフサイクルにフック可能と記載されています

実装を読み解く

以下は、404発生時に強制的に存在するページに恒久的にリダイレクトさせるコード実装例です。前述の、Nuxt4のプラグイン機能を使用しています。

// server/plugins/redirect-404.ts
import { getRequestURL, sendRedirect } from 'h3'
import type { H3Event } from 'h3'

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('error', async (error: any, ctx) => {
    const event = (ctx as any)?.event as H3Event | undefined
    if (!event) return
    const status = error?.statusCode ?? error?.status ?? 0

    if (status === 404) {
      const url = getRequestURL(event)
      const accept = event.node.req.headers['accept'] || ''
      const method = event.node.req.method || 'GET'

      const isHtml = typeof accept === 'string' && accept.includes('text/html')
      const isApi = url.pathname.startsWith('/api')
      const isRoot = url.pathname === '/'

      if (method === 'GET' && isHtml && !isApi && !isRoot) {
        return await sendRedirect(event, '/', 302)
      }
    }
  })
})

ポイントは次の通りです。

「error」フックでSSRレベルに介入する理由

Nitroの「error」フックは、サーバー処理中に投げられたエラー(H3エラーを含む)を一元的に捕捉できます。ここにロジックを置くと、Vueのレンダリングより前にHTTPレスポンスを決められるため、サーバー側で即時リダイレクトする設計と相性が良いです。

const event = (ctx as any)?.event as H3Event | undefined
if (!event) return

404のみを対象に限定している理由

status === 404で分岐しており、他の500系や権限系には触れていません。404だけをリダイレクト対象にすることで、想定外のエラーまでトップに飛ばして原因究明を難しくするリスクを避けています。

if (status === 404) {

「getRequestURL」でのURL判定

getRequestURL(event)H3の提供するAPIで、リバースプロキシ(例:Cloudflare、ALBなど)の「X-Forwarded-*」ヘッダーを考慮して完全なURLを生成します。多段プロキシ配下での挙動を安定させるには、このヘルパーを使うのが妥当です。

const url = getRequestURL(event)

「Accept」ヘッダーで「HTML」だけに限定

Accepttext/htmlが含まれるGETだけを対象にし、APIやアセットの誤作動を避けています。Nuxtのデフォルトでも、致命的なエラー時はAccept: application/jsonならJSON、そうでなければフルスクリーンエラーページを返すため、この判定はフレームワークの方針にも整合しています 。

引用:Error Handling – Nuxt v4
Accept: application/jsonヘッダーのときはJSONを、そうでなければエラーページを返す旨が明記されています

302で送る理由と「sendRedirect」の挙動

sendRedirect(event, '/', 302)は、Locationヘッダーを付与しデフォルト302で返すユーティリティです。ヘッダーが無視される環境向けに簡易なHTMLのメタリフレッシュもボディに含むため、互換性が高いのが特徴です。恒久移転なら301や308を選ぶ設計もあり得ますが、今回の「存在しないURL→ホーム」は通常一時的扱いのため302が無難です。

「/api」と「/」を除外する安全策

APIはJSONクライアントやWebhookなど非HTML文脈が多く、トップへ飛ばすとクライアントエラーになります。またトップ自身で404が出た場合のループを避けるためルートパスを弾いています。

ここが肝心:SEO観点での是非

サーバー側で404をホームへリダイレクトする実装は、UX上は「とりあえず生きているページに戻す」便利技ですが、検索エンジン的には推奨されません。

Googleは「存在しないページで404/410以外を返す、またはホームにリダイレクトする」ケースを「ソフト404」と呼び、ユーザーにも検索エンジンにも混乱を招き得ると明確に述べています。マーケ系の公開サイトでは、404ページを適切に返しつつ、検索やサイトマップ誘導を行う設計が安全です。

引用:404(ページが見つかりません)エラー - Search Console ヘルプ
存在しないページで404/410以外を返したりホームにリダイレクトしたりするのは「ソフト404」に該当し得ると解説されています

一方、ログイン必須の「アプリ型」体験や、社内向けツールなどクロール前提でない領域では、404→ホームの即時リダイレクトはUX優先の合理的な選択肢になり得ます。つまり「公開サイトのインデックス対象」か「非公開のアプリ」かで方針を分けることが重要です

実運用でハマりやすい点と対処のヒント

以下の注意点は、実際の運用でよく問題化します。チェックの前に結論を一文でまとめると、リダイレクトは「誰に対して」「どの環境で」発火するかを厳密にコントロールする必要があります。

要点

詳細

クローラー向けには404/410を返すべきである

「User-Agent」に「bot」を含む場合はリダイレクトを抑止するなどの分岐が有効である

APIや非HTMLのフェッチには絶対に適用しない設計である

Acceptヘッダーとパスプレフィックスで厳格に除外するのが定石である

HEAD・OPTIONSなどGET以外のメソッドは除外するべきである

GET以外に302を返すとクライアントの期待と齟齬が生じるためである

リバースプロキシ配下では「X-Forwarded-Proto/Host」を考慮するべきである

getRequestURLの挙動と設定を理解し、想定外のスキームやホストでの挙動を避けるべきである。

SSG/プリレンダリング時は404.htmlの発行戦略を別途設計すべき

ホスティングのルールと整合させないと意図せぬ挙動になり得るためである。

改善版のサンプル(ボット除外・HEAD除外・恒久移転の余地)

画像・API・ボットをより厳密に除外し、ヘッダーも少し丁寧に扱う改良例です。用途に応じてステータスコードは調整する必要があります。

// server/plugins/redirect-404-safer.ts
import { getRequestURL, sendRedirect } from 'h3'
import type { H3Event } from 'h3'

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('error', async (error: any, ctx) => {
    const event = (ctx as any)?.event as H3Event | undefined
    if (!event) return

    const status = error?.statusCode ?? error?.status ?? 0
    if (status !== 404) return

    const url = getRequestURL(event) // X-Forwarded-* を考慮
    const req = event.node.req
    const method = req.method || 'GET'
    const accept = String(req.headers['accept'] || '')
    const ua = String(req.headers['user-agent'] || '')

    // 非対象の早期return
    if (method !== 'GET') return
    if (!accept.includes('text/html')) return
    if (url.pathname.startsWith('/api')) return
    if (url.pathname === '/') return

    // ボットは正統な404を返す(ソフト404回避)
    if (/\b(bot|google|bing|baidu|yandex|duckduck|slurp)\b/i.test(ua)) return

    // 一時的扱いなら302、恒久移転なら301/308を選択
    return await sendRedirect(event, '/', 302)
  })
})

代替アプローチとの比較(いつどれを使うか)

以下では、Nuxtでリダイレクトや404ハンドリングを行う主要手段を並べ、強みと弱みを整理します。

表 Nuxtにおけるリダイレクト/404ハンドリング手段の比較

手段

どこで動くか

強み

注意点・弱み

代表的な用途

Nitroプラグイン+「error」フック

サーバー(Nitro)

SSRレベルで一括制御できる。APIや静的アセットを除外しやすい

設計を誤るとソフト404やAPI破壊のリスク。ボット除外が必須

非公開アプリ的サイトの404→ホーム誘導

ルートミドルウェア+navigateTo

クライアント/SSR両方

画面遷移の文脈で柔軟に分岐。ログインガードと相性が良い

既に404になった後の介入ではない。サーバーコードではないためSEO上の恒久リダイレクトには不向き

認証リダイレクト、UI都合の分岐

routeRules(nuxt.config)

ビルド時に各プラットフォームへ展開

宣言的に301/308等のルールを定義でき、SEO的に正しい恒久移転を表現しやすい

ワイルドカードや動的置換の制約がある。未知パス一括対応は不得手

旧URL→新URLの恒久移転(301/308)

~/error.vue(カスタムエラーページ)

Nuxtアプリ

404を正しく返しつつサイト内検索や導線を提供できる

リダイレクトはしないためURLは変わらない

公開サイトのUXとSEOの両立(正統404)

CDN/ホスティングのリダイレクトルール

エッジ

アプリに到達前に高速にさばける。恒久移転の一元管理がしやすい

プラットフォーム依存。未知パスの扱いは各社仕様に依存

大規模サイトの恒久リダイレクトやwww統合、HTTP→HTTPS強制

※ 表は各手段の一般的な特徴を比較し、本記事の文脈での使いどころを示したものです

Nuxt・H3の仕様に基づく補足

NuxtのエラーハンドリングはAcceptによりJSON応答とエラーページを切り替えます。このため「HTMLだけにリダイレクト」はフレームワーク方針との整合性が取れています 。sendRedirectはデフォルト302でLocationを付与し、メタリフレッシュのフォールバックも埋め込みます。クライアントの互換性リスクを下げられます。

尚、Acceptヘッダーの意味はHTTP標準の「コンテンツネゴシエーション」で、ブラウザは取得対象に応じて値を変えます。ここを根拠に「text/html」かどうかを判断すると安全です。

個人的見解

公開サイトでの404は「正しく404/410を返し、ユーザーには役立つエラーページを出す」のが原則です。今回のような「404→ホームの即時リダイレクト」は、社内向けツールやログイン必須のアプリで「URL直打ち時にもアプリ入口へ戻す」UXを優先したいときに限定的に採用するのが良いと考えます。

もし公開サイトで「旧URL→新URL」の移転を扱うなら、routeRulesやCDNの恒久リダイレクトを選び、404は404として返しておくのが安全です。

まとめ

今回のような「SSRレベルの即時リダイレクト」は強力なツールです。公開サイトでは「404は404で返す」を基本線に、アプリ体験では限定的に使う——この線引きをチームの設計基準として明文化しておくと、後々のトラブルを減らせます。

Careerバナーconsultingバナー