【完全版】Rails「ActionController::RoutingError」エラーの原因と対処法|No route matches を徹底解説

【完全版】Rails「ActionController::RoutingError」エラーの原因と対処法|No route matches を徹底解説

Ruby on Rails で頻発するエラー:

ActionController::RoutingError (No route matches [GET] "/users/profile")
ActionController::RoutingError (No route matches [POST] "/api/v1/users")
ActionController::RoutingError (No route matches [GET] "/assets/application-abc123.css")
No route matches {:action=>"show", :controller=>"users"}, missing required keys: [:id]

ActionController::RoutingError は「指定されたURLに該当するルートが存在しない」場合に発生します。Rails のリクエスト処理の最初の関門で起きるエラーで、

  • URL の typo が原因?
  • routes.rb の設定が間違っている?
  • HTTPメソッドが違う?
  • rescue_from で捕まえられない理由は?
  • 本番でカスタム404を表示したい
  • アセットでよく出るのは何?
  • セキュリティ的な対策は?

など、初学者から実務者まで遭遇する問題です。

本記事では、ActionController::RoutingErrorすべての原因と対処法を、現場で即使えるトラブルシューティングとして整理します。エラーの本質、rails routes の活用、catch-all route と exceptions_app、RecordNotFoundとの違い、APIモード対応、404ページカスタマイズ、デバッグ手法、FAQまで完全網羅。この1本でRails ルーティングエラーに自信を持って対処できます。


目次

結論:今すぐ試すべき3ステップ

時間がない方向けに、まず実施すべき手順を示します。

ステップ1:エラーから情報を取得

ActionController::RoutingError (No route matches [GET] "/users/profile")
                                                  ↑     ↑
                                               メソッド  リクエストURL

→ 「GETで /users/profile にアクセスしたが、対応するルートが無い」

ステップ2:現在のルート確認

bin/rails routes
# または
bin/rails routes | grep users

該当するルートがあるか確認。

ステップ3:原因別に対処

A. ルート未定義 → routes.rb に追加

# config/routes.rb
get "users/profile", to: "users#profile"
# または resources
resources :users do
  get :profile, on: :collection
end

B. HTTPメソッド違い → メソッド合わせる

<!-- GET なのに POST で送っている等 -->
<%= form_with url: users_path, method: :post do %>

C. typo → URL を修正

# /users/profile(正) vs /user/profile(誤)

詳細は以下で解説します。


まず押さえる:エラーの本質

ActionController::RoutingError とは

Rails のルーティングミドルウェアがリクエストURLに対応するルートを見つけられない時に発生する例外。

リクエスト → Rack → Routing → コントローラ
              ↓
        該当ルート無し
              ↓
   ActionController::RoutingError

Rails の自動404変換

ApplicationController のデフォルト動作として、Rails は以下のように扱う:

# rails/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
ActionDispatch::ExceptionWrapper.rescue_responses
# => {
#   "ActiveRecord::RecordNotFound" => :not_found,
#   "ActionController::RoutingError" => :not_found,  ← これ
#   ...
# }

つまり、本番環境では自動的に 404 になります

開発環境 vs 本番環境

環境RoutingError の挙動
development詳細エラー画面(routes 一覧表示)
testテスト失敗
production自動的に 404 ページ表示

RecordNotFound との違い

観点RoutingErrorRecordNotFound
発生レイヤールーティングコントローラ + Model
発生理由URLにマッチするroute無しレコードが見つからない
rescue_from効かない(後述)効く
本番でのデフォルト404404
/nonexistent_path/users/999

詳しい RecordNotFound 対策はActiveRecord::RecordNotFound の記事を参照。


なぜ rescue_from RoutingError は効かないのか

これは多くの開発者が引っかかる罠です。

期待する書き方

class ApplicationController < ActionController::Base
  rescue_from ActionController::RoutingError, with: :route_not_found
  # ↑ これでは動かない!
  
  private
  
  def route_not_found
    render template: "errors/not_found", status: :not_found
  end
end

なぜ効かないか

RoutingErrorコントローラに到達する前にミドルウェアレベルで発生するため、ApplicationControllerrescue_fromでは捕まえられません。

[リクエスト] 
    ↓ Rack 経由
[ActionDispatch::Routing]  ← ここで RoutingError 発生
    ↓ ルートマッチ
[ApplicationController]    ← rescue_from が動くのはここ

ミドルウェアで例外が出る前に、コントローラまで到達しないという仕組み上の問題。

解決策

方法特徴
catch-all routeroutes.rb で全URLを受ける
exceptions_appRails標準の例外ハンドラ
public/404.html静的404ページ

詳細は以下で解説します。


解決策1:catch-all route

config/routes.rb の末尾にcatch-all を置き、すべての未マッチURLをコントローラに送る方法。

基本実装

# config/routes.rb
Rails.application.routes.draw do
  root "home#index"
  resources :users
  resources :posts
  # ... 他のルート ...
  
  # 最後に catch-all
  match "*unmatched", to: "errors#not_found", via: :all
end
# app/controllers/errors_controller.rb
class ErrorsController < ApplicationController
  def not_found
    render status: :not_found
  end
end
<%# app/views/errors/not_found.html.erb %>
<h1>404 - ページが見つかりません</h1>
<p>お探しのページは見つかりませんでした。</p>
<%= link_to "トップへ", root_path %>

glob パターンの注意点

match "*unmatched", to: "errors#not_found", via: :all
  • *unmatched: 残りの全パスをキャプチャ
  • via: :all: GET/POST/PUT/PATCH/DELETE すべてに対応

resources より後に置く

# ❌ 上に置くと resources を上書きしてしまう
match "*unmatched", to: "errors#not_found", via: :all
resources :users  # ← これがマッチしない

# ✅ 必ず最後
resources :users
match "*unmatched", to: "errors#not_found", via: :all

config.after_initialize で append

エンジンgemのroutesがある場合、それらの後に追加するには:

# config/application.rb
config.after_initialize do |app|
  app.routes.append { match "*path", to: "errors#not_found", via: :all }
end

prepend ではなく append を使うのがポイント。


解決策2:exceptions_app(推奨)

Rails 3.2 以降の正式な方法。例外を独自のRack アプリにルーティング。

基本セットアップ

# config/application.rb
config.exceptions_app = self.routes

これでルーティング自体をエラーハンドラとして使用可能に。

# config/routes.rb
Rails.application.routes.draw do
  # 通常のルート
  root "home#index"
  resources :users
  # ...
  
  # エラーページのルート(exceptions_app で使われる)
  match "/404", to: "errors#not_found", via: :all
  match "/422", to: "errors#unprocessable_entity", via: :all
  match "/500", to: "errors#internal_server_error", via: :all
end
# app/controllers/errors_controller.rb
class ErrorsController < ApplicationController
  layout "error"
  skip_before_action :authenticate_user!, raise: false  # 認証スキップ
  
  def not_found
    @exception = request.env["action_dispatch.exception"]
    @original_path = request.env["action_dispatch.original_path"]
    render status: :not_found
  end
  
  def unprocessable_entity
    render status: :unprocessable_entity
  end
  
  def internal_server_error
    render status: :internal_server_error
  end
end

catch-all との違い

方法メリットデメリット
catch-all routeシンプル認証ミドルウェアやBefore Actionの影響を受ける
exceptions_appRails標準、認証等の干渉なし設定が若干複雑

本番運用なら exceptions_app 推奨

例外情報の取得

def not_found
  @exception = request.env["action_dispatch.exception"]
  @original_path = request.env["action_dispatch.original_path"]
  @original_request = request.env["action_dispatch.original_request"]
  
  Rails.logger.warn "404: #{@original_path}"
  
  render status: :not_found
end

解決策3:public/404.html(最も簡易)

何も設定しなくても、Rails は public/404.html を404レスポンスとして返します:

<!-- public/404.html -->
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>404 - ページが見つかりません</title>
  <link rel="stylesheet" href="/404.css">
</head>
<body>
  <h1>404 - ページが見つかりません</h1>
  <p>お探しのページは存在しません。</p>
  <a href="/">トップへ戻る</a>
</body>
</html>

メリット:

  • 設定不要
  • 静的ファイルなのでサーバー負荷ゼロ
  • Rails未起動でも返せる(Webサーバー側で配信)

デメリット:

  • 動的処理ができない(i18n、メッセージカスタマイズ等)
  • レイアウト統一が難しい

Webサーバー側での配信

Nginx などで配信:

location / {
    try_files $uri $uri/ @app;
    error_page 404 /404.html;
}

発生原因と対処パターン

原因1:未定義のURL

GET /users/profile
ActionController::RoutingError (No route matches [GET] "/users/profile")

確認:

bin/rails routes | grep profile

対処: routes.rb に追加

# config/routes.rb
get "users/profile", to: "users#profile"
# または
resources :users do
  collection do
    get :profile
  end
end

原因2:HTTPメソッドの不一致

ActionController::RoutingError (No route matches [POST] "/users/1")

POSTで送っているが、/users/1はPATCHやPUTを期待している:

bin/rails routes | grep users
# user PUT|PATCH /users/:id ...   ← PUT/PATCH が必要

対処: フォームのメソッドを修正

<%= form_with url: user_path(@user), method: :patch do %>

原因3:resources ネスティングの違い

ActionController::RoutingError (No route matches [GET] "/users/1/posts/2")
bin/rails routes | grep posts
# 単独の /posts/:id しかない

対処: nested resources で定義

resources :users do
  resources :posts
end

原因4:URL helperの誤用

<%= link_to "Edit", user_path(@user) %>
<%# user_path には id が必要! %>

エラー:

No route matches {:action=>"show", :controller=>"users"}, missing required keys: [:id]

対処:

<%# @user が nil でないか確認 %>
<%= link_to "Edit", user_path(@user) if @user %>

<%# または new_user_path 等を使う %>
<%= link_to "New User", new_user_path %>

原因5:フォーマット制約の不一致

# config/routes.rb
get "/users/:id", to: "users#show", format: :json
ActionController::RoutingError (No route matches [GET] "/users/1.html")
# HTMLが拒否される

対処: 制約を緩める or 明示

get "/users/:id", to: "users#show"   # 全フォーマット許可
# または
get "/users/:id", to: "users#show", defaults: { format: :json }

原因6:constraints の制限

constraints subdomain: "api" do
  resources :users
end

api.example.com 以外でのアクセスでルーティングエラー。

対処: subdomain や constraints の正当性確認。

原因7:アセットパスの間違い

ActionController::RoutingError (No route matches [GET] "/assets/application.css")

これは:

  • config.public_file_server.enabled が false
  • precompile していない
  • digest 付きファイル名の不整合
  • アセットパイプライン(Sprockets/Propshaft)の設定問題

対処:

# config/environments/production.rb
config.public_file_server.enabled = true
# または環境変数 RAILS_SERVE_STATIC_FILES=true
RAILS_ENV=production bin/rails assets:precompile

Propshaft 使い方の記事も参照。

原因8:concerns やネスト深さの問題

resources :users do
  concerns :commentable
end

concern :commentable do
  resources :comments
end

/users/1/comments/2/replies のように深くなると、ルートを正確に定義しないとエラー。

bin/rails routes でルート構造確認推奨。

原因9:DELETE/PATCH リクエストのCSRF問題

ActionController::RoutingError (No route matches [GET] "/users/1")

DELETE/PATCH リクエストがGETに変換されている場合。これは:

  • フォームのCSRF token不足
  • AJAX で適切なヘッダーが送られていない

CSRF関連エラーは別記事「ActionController::InvalidAuthenticityToken」参照。

原因10:本番デプロイ後のキャッシュ

Webサーバー(Nginx等)の routes キャッシュや、CDN キャッシュが古いことが原因。

対処: キャッシュクリア、サーバー再起動。


rails routes コマンドの活用

ルーティング問題の調査に必須のコマンド。

基本

bin/rails routes

出力例:

                                    Prefix Verb   URI Pattern                  Controller#Action
                                      root GET    /                            home#index
                                     users GET    /users(.:format)             users#index
                                           POST   /users(.:format)             users#create
                                  new_user GET    /users/new(.:format)         users#new
                                 edit_user GET    /users/:id/edit(.:format)    users#edit
                                      user GET    /users/:id(.:format)         users#show
                                           PATCH  /users/:id(.:format)         users#update
                                           PUT    /users/:id(.:format)         users#update
                                           DELETE /users/:id(.:format)         users#destroy
意味
PrefixURL helper の prefix(user_path等)
VerbHTTPメソッド
URI PatternURL パターン
Controller#Action処理するコントローラとアクション

フィルタリング

# 特定のキーワードを含むルート
bin/rails routes -g user

# 特定のコントローラ
bin/rails routes -c users

# 特定のフォーマット
bin/rails routes --format json

ブラウザでの確認

開発環境で http://localhost:3000/rails/info/routes にアクセス:

  • 検索可能なルート一覧
  • 各ルートの詳細

詳細表示

bin/rails routes --expanded

各ルートが複数行で詳細表示される。


APIモードでのカスタム404レスポンス

JSON API では JSON 形式の404を返したい。

exceptions_app 方式

# config/routes.rb
Rails.application.routes.draw do
  namespace :api do
    namespace :v1 do
      resources :users
    end
  end
  
  # エラー用
  match "/404", to: "errors#not_found", via: :all
  match "/500", to: "errors#internal_server_error", via: :all
end
# config/application.rb
config.exceptions_app = self.routes
# app/controllers/errors_controller.rb
class ErrorsController < ActionController::API
  def not_found
    render json: {
      error: {
        type: "NotFound",
        message: "リクエストされたリソースが見つかりません"
      }
    }, status: :not_found
  end
  
  def internal_server_error
    render json: {
      error: {
        type: "InternalServerError",
        message: "サーバーエラーが発生しました"
      }
    }, status: :internal_server_error
  end
end

catch-all route 方式(APIモード)

# config/routes.rb
Rails.application.routes.draw do
  namespace :api do
    namespace :v1 do
      resources :users
    end
  end
  
  # JSONレスポンス用 catch-all
  match "*unmatched", to: "errors#not_found", via: :all
end
class ErrorsController < ApplicationController
  def not_found
    respond_to do |format|
      format.json { render json: { error: "Not Found" }, status: :not_found }
      format.html { render template: "errors/not_found", status: :not_found }
      format.any  { head :not_found }
    end
  end
end

Rails-API モード固有

class Api::ApplicationController < ActionController::API
  # ApplicationController を継承しない場合
end

セキュリティ・ログ管理

Bot による大量404

GET /wp-admin
GET /.env
GET /admin
GET /phpmyadmin

このような攻撃的スキャンは日常的に発生。RoutingError ログが大量に出ます。

ログレベル調整

# config/application.rb
config.log_level = :info  # debugから上げる

または、RoutingError のログだけ抑制:

class ErrorsController < ApplicationController
  def not_found
    path = request.env["action_dispatch.original_path"]
    
    # 攻撃的なパターンは WARN レベルに下げる
    if suspicious_path?(path)
      Rails.logger.warn "Suspicious 404: #{path}"
    else
      Rails.logger.info "404: #{path}"
    end
    
    render status: :not_found
  end
  
  private
  
  def suspicious_path?(path)
    path =~ /(wp-admin|\.env|phpmyadmin|admin\/login)/i
  end
end

エラートラッカーで除外

Sentry/Rollbar/Honeybadger 等で RoutingError を除外(ノイズ削減):

# config/initializers/sentry.rb
Sentry.init do |config|
  config.excluded_exceptions += [
    "ActionController::RoutingError",
    "ActiveRecord::RecordNotFound"
  ]
end

Rack-Attack でレート制限

# config/initializers/rack_attack.rb
class Rack::Attack
  # 1分間に60リクエストまで
  throttle("req/ip", limit: 60, period: 1.minute) do |req|
    req.ip
  end
  
  # 不審なパスをブロック
  blocklist("malicious_paths") do |req|
    req.path =~ /(\.env|wp-admin|phpmyadmin)/i
  end
end

404ページの動的カスタマイズ

i18n 対応

<%# app/views/errors/not_found.html.erb %>
<h1><%= t("errors.not_found.title") %></h1>
<p><%= t("errors.not_found.description") %></p>
<%= link_to t("errors.not_found.back_home"), root_path %>
# config/locales/ja.yml
ja:
  errors:
    not_found:
      title: "ページが見つかりません"
      description: "お探しのページは削除されたか、URLが間違っている可能性があります。"
      back_home: "ホームに戻る"

検索ボックス表示

<%= form_with url: search_path, method: :get, class: "search-form" do %>
  <%= text_field_tag :q, params[:q], placeholder: "サイト内検索" %>
  <%= submit_tag "検索" %>
<% end %>

関連リンクの提案

def not_found
  @suggestions = SuggestionService.find_similar(request.path)
  render status: :not_found
end
<% if @suggestions.any? %>
  <h2>もしかして?</h2>
  <ul>
    <% @suggestions.each do |link| %>
      <li><%= link_to link.title, link.url %></li>
    <% end %>
  </ul>
<% end %>

アナリティクス埋め込み

<script>
  gtag('event', 'page_not_found', {
    'event_category': 'error',
    'event_label': '<%= @original_path %>'
  });
</script>

404発生パスを分析して、リンク切れの修正に役立てます。


トラブルシューティング

rescue_from RoutingError が効かない

前述の通り、これは Rails の仕様。exceptions_app または catch-all route を使ってください。

Production で 500 になる(404にならない)

# config/environments/production.rb
config.exceptions_app = self.routes  # ← 設定済みか確認

ErrorsController のアクションでエラーが起きていないか確認。

Development でも404ページを見たい

# config/environments/development.rb
config.consider_all_requests_local = false

これで開発環境でも本番と同じ404ページが表示されます。

Webpackerやアセットで頻発する

開発時:

# config/environments/development.rb
config.assets.compile = true

本番:

bin/rails assets:precompile

static_asset を返す設定

# config/environments/production.rb
config.public_file_server.enabled = true
# または環境変数
# RAILS_SERVE_STATIC_FILES=true

CDN/Nginx で配信するなら無効でOK。

Devise や認証ライブラリとの競合

class ErrorsController < ApplicationController
  skip_before_action :authenticate_user!, raise: false
  # before_action のスキップを忘れずに
end

エラーページで認証要求すると無限ループに。

test 環境で routes 関連エラー

# spec/rails_helper.rb
config.before(:each) do
  Rails.application.reload_routes!
end

routes.rb 変更後に test が古いルートを参照していないか確認。

POST/PATCH/DELETE がすべて GET になる

CSRF token の不足が原因の可能性。フォームでは:

<%= form_with model: @user do |f| %>
  <%# 自動でCSRFトークン付与 %>
<% end %>

API では:

class Api::ApplicationController < ActionController::API
  # ActionController::API は CSRFチェック自動スキップ
end

関連エラーとの違い

ActiveRecord::RecordNotFound

URLとマッチするrouteはあるが、DBレコードが無い。詳細はRecordNotFoundの記事

# /users/999 → routes はマッチ、Userレコードが無い
User.find(999)
# => ActiveRecord::RecordNotFound

NoMethodError

ルーティング後のコントローラ内でnilエラー。nil:NilClass記事参照。

AbstractController::ActionNotFound

ルートはあるが、コントローラのアクションが未定義。

# routes
get "users/special", to: "users#special_action"

# UsersController
# def special_action が定義されていない
# => AbstractController::ActionNotFound

ActionController::ParameterMissing

ルートにマッチしたが、必須パラメータが不足。

params.require(:user).permit(:name)
# params[:user] が無い
# => ActionController::ParameterMissing

違いの早見表

例外レイヤー
RoutingErrorルーティングURL未定義
ActionNotFoundコントローラアクション未定義
RecordNotFoundModelDBレコード不在
ParameterMissingコントローラ必須params不足

よくある質問(FAQ)

Q1. なぜ rescue_from RoutingError が動かない?

RoutingError は ApplicationController に到達する前のミドルウェアで発生するため。exceptions_app または catch-all route を使ってください。

Q2. 開発環境で404画面を確認したい

# config/environments/development.rb
config.consider_all_requests_local = false

これで本番と同じ404ページが見られます。

Q3. catch-all route と exceptions_app どちらを使うべき?

状況推奨
単純な404catch-all(簡単)
認証ミドルウェアありexceptions_app
Rails標準推奨exceptions_app

業務アプリでは exceptions_app 推奨。

Q4. catch-all route がエンジンのルートと競合します

# config/application.rb で append
config.after_initialize do |app|
  app.routes.append { match "*path", to: "errors#not_found", via: :all }
end

これで最後に追加され、エンジンルートを上書きしません。

Q5. APIで404をJSON形式で返す

class ErrorsController < ActionController::API
  def not_found
    render json: { error: "Not Found" }, status: :not_found
  end
end

Q6. 404 のログを抑制したい

def not_found
  path = request.env["action_dispatch.original_path"]
  Rails.logger.info "404: #{path}"   # debugではなくinfoに
  render status: :not_found
end

Sentry等のトラッカーで ActionController::RoutingError を除外設定も推奨。

Q7. リダイレクトで対処する

def not_found
  redirect_to root_path, alert: "ページが見つかりません"
end

ただし SEO 的には HTTP 404 を正しく返す方が良いです。

Q8. テストで RoutingError を確認する

# RSpec
it "存在しないパスで404" do
  expect { get "/nonexistent" }.to raise_error(ActionController::RoutingError)
end

# または raise_error しない形に
it "404 を返す" do
  get "/nonexistent"
  expect(response).to have_http_status(:not_found)
end

config.action_dispatch.show_exceptions = true の設定や、test 用の error route 設定が必要な場合あり。

Q9. POST/DELETE が GET に変換される

CSRF token が原因のことが多い:

<%# form_with は自動でCSRFトークン付与 %>
<%= form_with model: @user do |f| %>
<% end %>

Turbo を使う場合は data-turbo-method も使えます:

<%= link_to "削除", user_path(@user), data: { turbo_method: :delete } %>

Q10. アセットで404が頻発します

Production 環境の設定確認:

config.public_file_server.enabled = true

または、CDN/Nginxで静的ファイルを配信する構成に。

Propshaft 使い方の記事も参考に。

Q11. routes.rb が大きすぎて管理が困難

Rails 6+ の機能で分割可能:

# config/routes.rb
Rails.application.routes.draw do
  draw :api      # config/routes/api.rb を読み込む
  draw :admin    # config/routes/admin.rb
  
  # 通常のルート
  root "home#index"
end
# config/routes/api.rb
namespace :api do
  namespace :v1 do
    resources :users
  end
end

Q12. RoutingError のエラーレポートに自分のパスを含める

exceptions_app 内で:

def not_found
  @path = request.env["action_dispatch.original_path"]
  Rails.logger.warn "404 for: #{@path}"
  Sentry.capture_message("404: #{@path}") if Rails.env.production?
  render status: :not_found
end

不要に追跡する必要はありませんが、急増したパスの調査に有用。


参考リンク・関連資料

Rails 公式

ライブラリ

関連記事(本サイト)


まとめ

ActionController::RoutingError は Rails ルーティング層で発生する例外。要点を再整理します。

  • 本質: URLにマッチするルートが無い、ルーティング層で発生
  • rescue_from は効かない: ミドルウェア層で発生するため
  • 対処:
    • catch-all route: 簡易だが認証等の影響あり
    • exceptions_app: Rails標準推奨、認証等の干渉なし
    • public/404.html: 最も簡易
  • rails routes: ルート確認の必須コマンド
  • 本番自動404変換: production環境では自動的に404
  • 発生原因: 未定義URL、HTTPメソッド不一致、resources設定、URL helper誤用、フォーマット制約、アセット
  • API: JSON形式の404レスポンスを設定
  • セキュリティ: 攻撃的スキャンを Rack-Attack 等で制限
  • 404ページ: i18n、検索ボックス、サジェスト等で UX 向上
  • RecordNotFound との違い: ルーティング層 vs Model層

これらの知識は、Rails アプリの設計・本番運用・SEO最適化・セキュリティ強化など、あらゆる場面で活用できます。本記事をブックマークしておけば、ActionController::RoutingError に遭遇した時の対応が大幅に効率化されます。


本記事は2026年6月時点の情報をもとに、Ruby on Rails 7.x / 8.x での動作確認・公式ドキュメントに基づき作成しています。Rails のバージョンによって挙動が異なる場合があるため、最新の情報はRails公式ガイドもあわせてご確認ください。