はじめに
HerokuにRailsアプリをデプロイすると、以下のようなトラブルがよく発生します。
- ローカルでは表示されていた画像(
app/assets/images/
に保存していた画像)が表示されない! - 画像が表示されるはずのページで、"The page you were looking for doesn't exist."というエラー画面が出た!
そこで「Heroku rails 画像 表示されない」のようなキーワードで検索すると、この問題の対処法が載ったページがたくさん表示されます。しかし、以下のような情報が載っているページは間違いです。
🚨これは間違い!!
config/environments/production.rb
を開き、config.assets.compile = false
の値をtrue
に変更しましょう。
以下の対処法が載っている場合も多いですが、これもやはり間違いです。
🚨これも間違い!!
bin/rails assets:precompile RAILS_ENV=production
を実行し、public/assets
ディレクトリに生成されたファイルをコミットしてHerokuにpushしましょう。
というわけで、この記事ではHerokuで画像が表示されない原因と、その原因に対する適切な対処法を説明します。
対象バージョン
- Rails 7.0.2.2
- sprockets-rails 3.4.2
- sprockets 4.0.2
サンプルアプリケーションのソースコード
この記事で使っているサンプルアプリケーションにはサンプルコードがあります。自分のRailsアプリケーションがうまく動かないときはこの実装を参考にしてみてください。
サンプルアプリをHerokuで動かしてみる
このサンプルアプリはHerokuにデプロイ済みです。以下のURLにアクセスすると、このあとで説明する原因1〜3のエラーを実際に体験できます。
また、 config.assets.compile = true
を設定して、「あたかもこの問題が解決したように見えるバージョン(NGバージョン)」も作っています。このURLにアクセスすると、一見どのページもちゃんと動いているように見えるのですが、後述する理由により、 適切な解決策にはなっていません。
それでは以下が本編です。
原因1: image_tagに渡すファイルに拡張子が付いていない
app/assets/images/bread.jpg
という画像ファイルがあったとします。この画像ファイルを以下のように拡張子を付けない形式で参照していると、ローカルでは表示されるのにHerokuにデプロイすると"The page you were looking for doesn't exist."のエラーが発生します。
<!-- NG: 拡張子が付いていない! -->
<%= image_tag "bread" %>
Herokuのログを見ると以下のようなエラーが出力されているはずです。
Completed 500 Internal Server Error in 1ms (Allocations: 513)
ActionView::Template::Error (The asset "bread" is not present in the asset pipeline.
1: <h1>Welcome!</h1>
2: <p>Welcome to my homepage.</p>
3: <div class="my-image">
4: <%= image_tag 'bread' %>
5: </div>
app/views/no_exts/show.html.erb:4
ブラウザ上の画面はこうなっています。
対処法: 適切な拡張子を付ける
これは画像ファイルの拡張子が指定されていないのが原因です。以下のように適切な拡張子(jpg、png、gifなど)を付ければHerokuで画像が表示されます。
-<%= image_tag "bread" %>
+<%= image_tag "bread.jpg" %>
Tips: 常に拡張子を表示するようにOSを設定しよう
Macの場合、デフォルトではファイルの拡張子が表示されないケースがあるため、プログラミング初心者はこのエラーを起こしやすい(拡張子が付いてないと勘違いしやすい)かもしれません。ですので、Finder上では常に拡張子を表示するように設定を変更しておくことをお勧めします。
原因2: 拡張子が"jpeg"になっている
JPG画像の拡張子が"jpeg"になっていると、やはりローカルでは表示されるのにHerokuにデプロイすると"The page you were looking for doesn't exist."のエラーが発生します。
<!-- NG: 拡張子がjpegになっている! -->
<%= image_tag "bread.jpeg" %>
Herokuのログを見ると以下のようなエラーが出力されているはずです。
Completed 500 Internal Server Error in 2ms (Allocations: 1011)
ActionView::Template::Error (The asset "bread.jpeg" is not present in the asset pipeline.
1: <h1>Welcome!</h1>
2: <p>Welcome to my homepage.</p>
3: <div class="my-image">
4: <%= image_tag 'bread.jpeg' %>
5: </div>
app/views/use_jpegs/show.html.erb:4
ブラウザ上の画面はこうなっています。
対処法: コード上のJPG画像の拡張子は"jpg"で統一する
Railsはassets:precompileを実行すると、JPG画像の拡張子を"jpg"に統一します。そのため、コード上の拡張子が"jpeg"になっているとファイルが参照できず、エラーが発生します。以下のように拡張子を"jpg"にすればHerokuで画像が表示されます。
-<%= image_tag "bread.jpeg" %>
+<%= image_tag "bread.jpg" %>
app/assets/images/
に格納されている画像の拡張子も"jpeg"になっている場合は、無用なトラブルを避けるため、あわせて"jpg"に変更しておきましょう。
git mv app/assets/images/bread.jpeg app/assets/images/bread.jpg
原因3: "/assets" で始まるパスを指定している
app/assets/images/bread.jpg
という画像ファイルを image_tag "/assets/bread.jpg"
のように "/assets" で始まるパスで指定していると、ローカルでは表示されるのに、Herokuでは「画像が見つかりません」アイコンが表示されます。
<!-- NG: /assetsでパスが始まっている! -->
<%= image_tag "/assets/bread.jpg" %>
Herokuで表示した場合
Chromeの開発者ツールでネットワークタブを開くと、画像のステータスコードが "404 Not Found" になっているはずです。
対処法: "/assets"を付けずに"app/assets/images" における相対パスを指定する
image_tag
に渡すパスは "/assets" で始めず、 "app/assets/images" における相対パスを指定してください。
たとえば、表示させたい画像ファイルのパスがapp/assets/images/bread.jpg
であれば、image_tag
に渡すパスはapp/assets/images/
を取り除いたbread.jpg
だけでOKです。
-<%= image_tag "/assets/bread.jpg" %>
+<%= image_tag "bread.jpg" %>
もしapp/assets/images/foo/bread.jpg
のように、imagesディレクトリ内でサブディレクトリが切られている場合は image_tag "foo/bread.jpg"
のように書きます。
背景画像が表示されない場合も原因と対処法は同じ
image_tag
だけでなく、CSSで背景画像を表示している場合も「ローカルでは背景画像が表示されているのに、Herokuに上げたら表示されなくなった!」というトラブルが起きることがあります。
Herokuでの表示
背景画像が出なくなった場合はCSS(scssやsass)でbackground-image
を指定している箇所を探してください。原因と対処法はimage_tag
の場合と同様です。
たとえば、画像のパス(URL)が"/assets"から始まっているようであれば、"/assets"なしのパスを指定してください。
以下は app/assets/images/bg.png
という画像ファイルを背景画像に指定する場合の修正例です。
.my-image {
- background-image: image-url('/assets/bg.png');
+ background-image: image-url('bg.png');
}
(2024.6.15追記)
image-url
が有効なのはsass-railsまたはsassc-railsを使っているときだけです。
dartsass-railsを使っている場合はimage-url
の代わりにurl
を使ってください。
.my-image {
- background-image: image-url('/assets/bg.png');
+ background-image: url('bg.png');
}
sprockets-rails 3.3.0以上がインストールされていれば、url
で指定した画像のパスが自動的にダイジェスト付きのパスに変換されます。
.my-image {
background-image: url(/assets/bg-31822d53e9eb35ebd579f796344cbc52362df4f2ed952c71635b8ee919f2a19b.png);
}
Sassを利用するためのgemと使える関数の関係については以下のブログで詳しく説明しています。
上の対処法でも解決しない場合(情報募集中)
Heroku上で画像が表示されない問題にはいくつかパターンがあるようです。僕の方で確認できたパターンは上の3つですが、このほかにもまだあるかもしれません。
もし「上の対処法はどれも使えず、結局 config.assets.compile = true
にして解決させました」という場合は、詳しい状況を聞いてみたいのでコメント欄等で「うまくいきません!」と名乗り出てください
HerokuにデプロイするとCSSやJavaScriptが効かないときは・・・?(情報募集中)
ネットの記事を見ていると、他にも「HerokuにデプロイしたらCSSやJavaScriptが効かなくなった(デザインが崩れた or JSが動かない)」という問題もよく起きているようです。
ただ、これについては僕の手元では状況を再現できませんでした。もし、この記事を読んでいる人で上記の症状に遭遇した人がいたら、詳しい状況を聞いてみたいのでコメント欄等で「CSSやJSが効きません!」と名乗り出てください
stylesheet_pack_tag
を使っていないせい?
(2022.2.26追記)
以下の条件がすべてYESの場合、ローカルでは正常に適用されているCSSがHerokuデプロイ時に適用されないケースがあります。
- Webpackerを利用している
- JS内でCSSをimportしている
- layoutファイル内で
stylesheet_pack_tag
を使わず、javascript_pack_tag
だけしか使っていない
この場合、layoutファイルを編集して stylesheet_pack_tag
でCSSを読み込むようにして下さい。こうすると本番環境でWebpackerが生成したCSSファイルが読み込まれるようになります。
<%= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
ローカル環境では config/webpacker.yml
の中にある extract_css
が false になっており、JSの力で動的にCSSを適用するため、 javascript_pack_tag
だけでCSSが適用されます。
一方、本番環境では extract_css
が true になっていて静的なCSSファイルが出力されます。そのため、 stylesheet_pack_tag
を使ってCSSファイルを別途読み込む必要があります。
なお、ネット記事の中には「 extract_css
を true から false に変更する(つまり開発環境と同じにする)」という解決策が載っている場合がありますが、これは以下のようなデメリットがあるため推奨されません。
- CSSが静的なファイルではなく、JSの力で動的に適用する形式になってしまう
- ブラウザが常にJSとCSSをセットでキャッシュしなければいけなくなる
- JSの読み込みが終わるまでCSSが適用されないため、一瞬表示が崩れる
参考 https://stackoverflow.com/questions/43417739/why-extract-css/43424122#43424122
Q. "config.assets.compile = true"じゃダメなんですか? → はい、ダメです🙅♂️
ところで、実はここまで説明してきた問題はすべて、 config/environments/production.rb
の config.assets.compile = false
の値を true
に変更すれば解決します。この方法はめちゃくちゃ手っ取り早いです。いちいち自分が書いたコードを調べて修正しなくてもパッと解決してしまうので。
ですが、この方法はお勧めしません。なぜならアプリケーションのパフォーマンスに悪影響を及ぼすからです。
Herokuのヘルプページにもその旨が明記されています。
本番環境でアプリケーションの
config.assets.compile = true
を有効にすると、アプリケーションが非常に低速になる場合があります。
(中略)
この設定は、実行時にその他の不安定な動作の原因になることも知られているため、通常は推奨されません。
また、デプロイ時にも以下のような警告が出力されます。
###### WARNING:
You set your `config.assets.compile = true` in production.
This can negatively impact the performance of your application.
For more information can be found in this article:
https://devcenter.heroku.com/articles/rails-asset-pipeline#compile-set-to-true-in-production
以下は上の警告の筆者訳です。
警告:
あなたは本番環境でconfig.assets.compile = true
を設定しています。
この設定はアプリケーションのパフォーマンスに悪影響を及ぼす可能性があります。詳しい情報は以下の記事をご覧ください。
https://devcenter.heroku.com/articles/rails-asset-pipeline#compile-set-to-true-in-production
ざっくり言うと、 config.assets.compile = true
は開発環境(ローカル環境)向けの便利機能です。実行速度の遅さと引き換えに、コードを修正したら即座にブラウザ上にその変更を反映させる機能です。
しかし、本番環境ではトライアンドエラーを繰り返しながらコードを変更するようなことはありません。なので、開発者向けの便利機能を提供するよりも実行速度を重視して config.assets.compile = false
がデフォルトで設定されています。
ちなみに、ネット記事を見ていると「 config.assets.compile = true
にすれば高速化される」と説明しているものもたまにありますが、それは完全に真逆の説明です!
ただし、Rails側に問題がないとも言えない🤔
ただ、このページの説明を読んでもらえばわかるとおり、Railsは「本番環境で動かないコードがローカル環境でふつうに動いてしまう」という問題を抱えています。
基本的にローカルと本番では(実行速度の違いはあれど)同じように動くのが理想的です。本番で動かないコードはローカルでも動かないようになっているのが自然ですし、その逆もまた然りです。
この問題は特に、プログラミング初心者さんにとっては非常に優しくない仕様だなと思います。原因の特定も難しいので、 config.assets.compile = true
で手っ取り早く解決したくなる気持ちもよくわかります。
この問題についてはGitHubですでにissueがいくつか上がっているようですが、なぜか改善される気配がありません。いったいなぜなんでしょう?
- image_path without extension · Issue #35651 · rails/rails
- Rails crashes in production but not dev/test when accessing image_url for image without extension · Issue #42784 · rails/rails
- Image without extension works in Dev environment but raises error in Production · Issue #729 · rails/sprockets
その他のよくある間違った解決策
以下はHerokuで画像が表示されない場合の、その他のよくある間違った解決策です。
ローカルでassets:precompileしてからデプロイする?
「 bin/rails assets:precompile RAILS_ENV=production
を実行し、 public/assets
ディレクトリに生成されたファイルをコミットしてHerokuにpushしましょう」みたいな解決策もよく見かけます。ですが、僕の手元ではこれを試しても画像が表示されない問題は解決しませんでした。
僕が調べた限り、「ローカルでassets:precompileしたら直った」と書いてる記事のほとんどで、「config.assets.compile = true
にしました」という記述も一緒に書かれていました。そのため、問題が解決したのはおそらくローカルで実行したassets:precompileではなく、 config.assets.compile = true
のおかげではないかと想像しています。
assets:precompileはデプロイ時に自動的に実行されるはず
なお、Herokuではデプロイ時に自動的にassets:precompileが実行されるようになっています。ローカルでassets:precompileするのも、Herokuがデプロイ時にassets:precompileのも、基本的にやっていることは同じなので、「ローカルでassets:precompileすれば画像が表示される」というのは問題を解決できた理由として考えにくいです。
仮に「ローカルでassets:precompileしてからコミットし、デプロイする」という方法で解決したとしても、それ以降はassets(CSSやJavaScript、画像等)を変更するたびに「忘れずにプリコンパイルする」という作業がつきまといます。「忘れずに○○する」という作業ほど厄介なものはありません。こうなると「プリコンパイルのし忘れで本番環境でバグった」みたいな不具合も起きやすくなるので、この方法を解決策と見なすのはお勧めできません。
参考情報: Herokuが自動的にassets:precompileするとき、しないとき
public/assets/manifest-<md5 hash>.json
または public/assets/.sprockets-manifest-<md5 hash>.json
がコードに含まれていると、Herokuはデプロイ時にassets:precompileを実行しません。
過去の試行錯誤の過程でローカルにpublic/assets/
ディレクトリが存在しているような場合はHerokuにデプロイしても自動的にassets:precompileされない可能性があるので、public/assets/
ディレクトリを削除した方が良いでしょう。
詳しい内容は以下のヘルプページを読んでください。
"config.public_file_server.enabled = true"を設定する?
「config/environments/production.rb
を開いて config.public_file_server.enabled = true
を設定する」という解決策もときどき見かけますが、これも意味がありません。
なぜなら、HerokuではRailsアプリケーションをデプロイするとデフォルトで RAILS_SERVE_STATIC_FILES
という環境設定が設定され、最初から true
(と同等の値)になっているためです。
ちなみに config/environments/production.rb
のデフォルトのコードは以下です。
# Disable serving static files from the `/public` folder by default since
# Apache or NGINX already handles this.
config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present?
"config.assets.digest = false"を設定する?
「config/environments/production.rb
を開いて config.assets.digest = false
を設定する」という解決策もたまに見かけますが、この情報はかなり古いです。現状のRailsはassets:precompile時に常にdigest付きのファイル名を出力します。なので、この設定を変更しても何も変わりません。
"config.assets.initialize_on_precompile = false" を設定する?
この設定も上の config.assets.digest = false
と同様、現在では使われない設定値です。なので、この設定を変更しても何も変わりません。
"config.serve_static_files = true" を設定する?
この設定も現在では使われない設定値です。なので、この設定を変更しても何も変わりません。
"config/environments/production.rb" の設定値まとめ
ここまで書いてきた、config/environments/production.rb
の設定値に関する内容をまとめると以下のようになります。
# trueだとパフォーマンスが落ちます。デフォルトのfalseに戻してください。
config.assets.compile = true
# trueでも良いですが、Herokuが環境変数を設定してくれるので、元々書かれていた
# ENV["RAILS_SERVE_STATIC_FILES"].present? に戻しておきましょう。
config.public_file_server.enabled = true
# 以下の設定は最近のRails(6以降?)では使われていないので削除しましょう。
config.assets.digest = false
config.assets.initialize_on_precompile = false
config.serve_static_files = true
つまり、config/environments/production.rb
はデフォルトのままで良い(問題があればviewやcssを修正する)、ということになります。
# config/environments/production.rbはデフォルト値のままでOK!
config.assets.compile = false
config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present?
さらに:アセットプリコンパイルやHerokuのデプロイ手順について詳しく学ぶ
Railsのアセットプリコンパイル(アセットパイプライン)について詳しく学びたい場合はRailsガイドを読みましょう。
ネット上には初心者向けに易しく解説した記事もありますが、不正確な情報が含まれていたり、情報がすでに古くなっていたりすることがあります。なので、なるべく公式の情報を読むことをお勧めします。
また、Herokuへのデプロイ手順についても公式のドキュメントを参照するようにしましょう。
ネット上には「オレオレHerokuデプロイ手順」がたくさんありますが、僕からすると「ちゃんと公式ドキュメント読んでる?」と思うものが非常に多いです。公式ドキュメントには「 config.assets.compile = false
を true
に変えましょう」なんて、どこにも書いてありませんよ!
一般論として: なぜデフォルトの設定値が違うのか?その理由を考えよう
今回の config.assets.compile
に限らず、Railsのデフォルト設定がローカル環境( development.rb
)と本番環境( production.rb
)で異なるケースは他にもあります。そして、そこには必ず何かしらの理由(Rails開発チームの意図)があるはずです。
その理由を考察せずに、「よくわかんないけど、ネット記事にそう書いてあったから」とおまじないのように設定を変えてしまうのは非常に危険です。
本番環境も開発環境と同じ動きにしたい、でもなぜ設定が異なるのかはわからない、本番環境でその設定を変更したときのデメリットやリスクも理解していない、というプログラミング初心者さんは、設定を変える前に必ず上級者に質問・相談するようにしてください。
独学で勉強していて頼れる上級者が身近にいない、という場合はTeratailやQiitaの質問機能といった開発者向けQ&Aサイトで質問するようにしましょう。
まとめ
というわけで、この記事ではRailsアプリをHerokuにデプロイしたときに画像が表示されない場合の適切な対処法を説明しました。
ネットを検索すると「 config.assets.compile = true
にすれば解決しました!」と書いている記事が山のように出てきます。ですが、本番環境で config.assets.compile = true
にしなければならないケースは滅多にありません。僕自身、10年近く実務でRailsアプリを開発していますが、 config.assets.compile = true
で本番デプロイしたことは一度もありません。また、ローカルでassets:precompileしてコミットする対処法も同様にやったことがありません。
Herokuデプロイ時に画像が出ないトラブルは、View(erbファイル)やCSSの書き方に問題があるケースがほとんどだと思います。この記事を読んだみなさんは安易な config.assets.compile = true
やローカル環境での assets:precompile に頼らず、ViewやCSSの記述を見直しましょう。
もし自力で解決できなかった場合は熟練者にヘルプを求めてください。最近Qiitaにも質問ページができたので、ここで質問するのも良いと思います。
おまけ: 人類にはまだ早すぎたアセットプリコンパイル?
Qiitaで「"config.assets.compile" heroku」というキーワードで検索すると、102件もの記事が検索結果に上がってきます(2022年2月13日時点の結果です)。
全部の記事を見たわけではありませんが、この中で正しい解決方法を説明している記事はほんの数件しかありませんでした。
Twitter上にも「 config.assets.compile = true
にすれば解決しました!」という発言が多数見つかります。
また、GitHubを検索しても、国内外を問わず、production.rb
にconfig.assets.compile = true
を含むリポジトリが大量に見つかります。
こういう結果を見ると、「人類にはまだアセットプリコンパイルは早かったのではないか?」という気がしてきますね……😅