How to Fix Rails Assets Not Loading in Production: Sprockets vs Propshaft

Blog Main Image

Rails assets not loading in production is one of the most common deployment issues in Ruby on Rails applications. An app that is built on Rails may look perfect in the development stage, but right after deployment, the stylesheet wouldn’t be the same. JavaScript stops working, the image start breaking, and the browser console started to show the 404 not found error.

The reason behind this that deployment doesn’t handle the assets in the same way development does. In production, Rails expects assets to be compiled, fingerprinted, mapped through a manifest file, and served either by the Rails app, a web server, or a CDN.

The exact fix depends on your asset pipeline. Older applications usually depend on Sprockets Rails, while new Rails 8 applications use Rails Propshaft by default. Rails 8 release notes confirm that Propshaft is used by default, and the Rails asset pipeline guide explains that Propshaft now manages Rails asset management for new applications.

This guide explains why Rails assets fail in production, how Sprockets and Propshaft differ, and how to fix production asset issues in a technically correct way.

If you are building or maintaining a production Rails application, strong web architecture matters beyond the asset pipeline. Amrood Labs provides custom web development services using technologies such as Ruby on Rails, React, Node.js, Python, and cloud platforms.

Understanding Rails Asset Management in Production

Rails asset management is the process of organizing, fingerprinting, caching, and serving static files such as CSS, JavaScript, fonts, and images. In development, Rails can serve assets directly from the application folders. In production, Rails usually expects those files to be prepared before requests hit the live app.

For example, this logical file:

application.css

may become this fingerprinted production file:

application-8d4f3a9c.css

The fingerprint, also called a digest, helps browsers cache files safely. When the file changes, the digest changes. This lets Rails deliver assets for Rails applications with long cache headers while still forcing browsers to fetch updated files after deployment.

A production Rails app needs three things for assets to work correctly:

  1. The asset files must be compiled or copied into public/assets.
  2. A manifest file must map logical names to fingerprinted filenames.
  3. The server must be able to serve files from public/assets.

If any of these steps fail, Rails assets may not load in production.

Common Symptoms of Rails Assets Not Loading in Production

You are probably dealing with an asset pipeline issue if you see any of the following:

  • GET /assets/application.css 404
  • GET /assets/application.js 404
  • The asset "logo.png" is not present in the asset pipeline
  • AssetNotPrecompiled
  • Missing public/assets/.manifest.json
  • CSS loads in development but not in production
  • JavaScript works locally but fails after deployment
  • Images show as broken after deploy

These symptoms can happen in both Rails Sprockets and Rails Propshaft setups, but the cause and fix may differ.

Sprockets Rails vs Rails Propshaft

Before fixing the issue, it helps to understand the difference between Sprockets and Propshafts.

What is Sprockets Rails?

Sprockets was the classic Rails asset pipeline for many years. It can compile and serve web assets, and the official Sprockets repository describes it as a Rack-based asset packaging system with dependency management and a preprocessor pipeline for JavaScript and CSS assets.

Sprockets can handle tasks such as:

1. CSS and JavaScript preprocessing

2. Asset concatenation

3. Dependency directives

4. Fingerprinting

5. Manifest generation

6. Serving compiled assets

Sprockets uses directives such as:

//= require_tree

and manifest entries such as:

//= link_tree ../images
//= link_directory ../stylesheets .css
//= link_directory ../javascripts .js

//= link_tree ../images

//= link_directory ../stylesheets .css

//= link_directory ../javascripts .js

What is Rails Propshaft?

Propshaft is the newer asset pipeline used by default in Rails 8. The official Propshaft documentation says that Rails 8 uses Propshaft as the default asset pipeline for new applications.

Propshaft is intentionally simpler than Sprockets. It focuses on:

1. Asset lookup

2. Digest stamping

3. Manifest mapping

4. Serving fingerprinted assets

It does not try to be a full build system. It does not replace esbuild, Bun, Rollup, Tailwind, Sass, or PostCSS. The Rails asset pipeline guide explains that Propshaft mainly focuses on essential asset management tasks and leaves JavaScript/CSS bundling and minification to other reliable tools such as jsbundling-rails and cssbundling-rails.

This is an important point for any rails propshaft guide: Propshaft is for delivery and fingerprinting, not full frontend compilation.

Rails 8 Propshaft and Importmap by Default

Rails 8 continues the direction introduced in Rails 7: less default frontend build complexity and more reliance on browser-native capabilities.

In a typical Rails 8 application:

ü  Propshaft handles asset delivery, fingerprinting, and manifest generation.

ü  Importmap handles JavaScript module resolution.

ü  External build tools are added only when the application needs them.

This means Rails assumes many apps can work well with native ES modules, HTTP/2, Hotwire, Stimulus, and import maps. The Rails JavaScript guide explains that Rails supports using JavaScript without Node.js, Yarn, or a JavaScript bundler, while still giving options such as esbuild, Rollup, Webpack, and Bun when bundling is needed.

For CRUD-heavy SaaS apps, admin panels, marketplaces, dashboards, and standard business platforms, Rails 8 Propshaft with Importmap can be enough. For React, Vue, or frontend-heavy products, you may still need jsbundling-rails, cssbundling-rails, Vite, esbuild, or another bundler.

Why Rails Assets Fail in Production

Production asset failures usually come from one of these causes:

  • assets:precompile was not run
  • public/assets is empty
  • the manifest file is missing
  • static file serving is disabled
  • asset paths are hardcoded
  • Sprockets manifest.js is incomplete
  • Propshaft asset paths are misconfigured
  • jsbundling or cssbundling output is missing
  • Docker or CI skipped the asset build step
  • Nginx or Apache is not serving public/assets
  • CDN cache is serving old asset URLs

Let’s go through the fixes step by step.

Fix 1: Run Asset Pre-compilation

The primary reason is the absence of precompiled files in the production stage. Run the following command on your server:

RAILS_ENV=production bundle exec rails assets:precompile

For older Rails apps, you may also see:

RAILS_ENV=production bundle exec rails assets:precompile

If you suspect stale compiled files, clear and rebuild them:

RAILS_ENV=production bundle exec rails assets:clobber
RAILS_ENV=production bundle exec rails assets:precompile

Then check:

ls public/assets

Check for the fingerprinted CSS, JavaScript, font, and manifest files.

Verify that public/assets should have a file because if it is empty, your production server has nothing to serve. In this situation, the actual problem is not the browser, CSS or CDN. The build step did not produce the expected files.

Fix 2: Verify the Manifest File

Rails uses a manifest file to map logical asset names to fingerprinted production files.

With Sprockets, you may see something like:

public/assets/.sprockets-manifest-xxxx.json

With Propshaft, you should see:

public/assets/.manifest.json

The Propshaft documentation states that logical references are converted into digest-aware paths in production after assets:precompile runs, using the JSON mapping file at public/assets/.manifest.json.

If the manifest is missing, Rails may not know that:

application.css

should point to:

application-8d4f3a9c.css

That can cause broken CSS, broken JavaScript, or missing images.

Fix 3: Enable Static File Serving

Sometimes assets are compiled correctly, but the production server is not serving them.

Check this setting:

# config/environments/production.rb 
config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present?

If your platform expects Rails to serve static files, set:

RAILS_SERVE_STATIC_FILES=true

This is common on platforms where the app server handles static assets directly. On VPS setups, Nginx or Apache often serves files from the Rails public directory instead.

For Nginx, a common assets block looks like this:

location /assets/ {
   root /var/www/myapp/public;
   gzip_static on;
   expires max;
   add_header Cache-Control public;
 }

Note: Make sure the Nginx "root" points to the correct Rails "public" directory. If Nginx points somewhere else, /assets/application-xxxx.css will return 404 even if the file exists.

Fix 4: Use Rails Asset Helpers

Hardcoded asset paths are a major reason Rails assets fail in production.

Avoid this:

<img src="/assets/logo.png"> 
<link rel="stylesheet" href="/assets/application.css"> 
<script src="/assets/application.js">
  
</script>

Use Rails helpers instead:

<%= image_tag "logo.png" %> 
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> 
<%= javascript_include_tag "application", "data-turbo-track": "reload", defer: true %>

Helpers read the manifest and generate the correct fingerprinted production path. A hardcoded path usually points to a file that does not exist after fingerprinting.

This matters in both Sprockets Rails and Rails Propshaft.

Fix 5: Sprockets-Specific Checks

If your app uses Sprockets, start with the Gemfile:

gem "sprockets-rails"

Then check the manifest:

// app/assets/config/manifest.js
//= link_tree ../images
//= link_directory ../stylesheets .css
//= link_directory ../javascripts .js

If you have separate admin, dashboard, or marketing entry files, link them explicitly:

//= link admin.css
//= link dashboard.js
//= link marketing.css

If Rails raises AssetNotPrecompiled, it usually means the asset is not included in the production manifest or precompile list.

Some developers temporarily use:

config.assets.compile = true

This may help during debugging, but it should not be treated as the main production fix. A proper production setup should precompile assets during deployment or CI.

Fix 6: Propshaft-Specific Checks

If your app uses Rails 8 Propshaft, check:

gem "propshaft"

Then check which paths are part of the asset load path. Propshaft copies assets from configured config.assets.paths into public/assets during precompilation. This behavior is documented by Propshaft and is different from older Sprockets expectations.

This explains a common Rails 8 Propshaft question:

Why is Propshaft generating digested files for many CSS and JS files instead of only application.css and application.js?

In many cases, this is expected. Propshaft treats files in the asset load path as assets to copy and fingerprint. If your raw source files are inside an asset path, Propshaft may copy them too.

If a directory is only used as input for a compiler, exclude it correctly:

# config/application.rb 
config.assets.excluded_paths << Rails.root.join("app/assets/stylesheets") 
config.assets.excluded_paths << Rails.root.join("app/javascript")

Do not write it like this:

# WRONG - creates nested array AND paths aren't absolute
 config.assets.excluded_paths << ["app/assets/stylesheets", "app/javascript"]

 # CORRECT - use += for multiple paths with full absolute paths
 config.assets.excluded_paths += [
   Rails.root.join("app/assets/stylesheets"),
   Rails.root.join("app/javascript")
 ]

 # OR use << for single paths (one at a time)
 config.assets.excluded_paths << Rails.root.join("app/assets/stylesheets")
 config.assets.excluded_paths << Rails.root.join("app/javascript")

Propshaft expects full paths for excluded directories. The Propshaft documentation specifically mentions using full paths such as Rails.root.join("app/assets/stylesheets") when excluding compiler input directories.

Fix 7: Check jsbundling and cssbundling Output

Propshaft does not compile advanced JavaScript or CSS by itself. If your app uses esbuild, Bun, Rollup, Tailwind, Sass, or PostCSS, make sure the build output exists before Propshaft fingerprints assets.

Common output files:

app/assets/builds/application.js
app/assets/builds/application.css

Run:

yarn build
yarn build:css

or:

npm run build
npm run build:css

Then run:

RAILS_ENV=production bundle exec rails assets:precompile

The production flow should be:

Ø  Bundler compiles JavaScript and CSS

Ø  Output goes to app/assets/builds

Ø  Propshaft or Sprockets fingerprints the output

Ø  Files are copied to public/assets

Ø  Rails or the web server serves the fingerprinted files

If app/assets/builds/application.css does not exist, stylesheet_link_tag "application" may fail or point to a missing file.

Fix 8: Check CSS Image URLs

Images referenced inside CSS are another common production issue.

With Propshaft, CSS asset URLs can be rewritten to digest-aware paths. The Propshaft docs explain that it can translate CSS url(asset) style references to digested asset URLs.

For example:

.hero {
  background-image: url("/bg/pattern.svg");
}

may become:

.hero {
  background-image: url("/assets/bg/pattern-2169cbef.svg");
}

For this to work, the referenced file must exist in the asset load path. If the image is outside app/assets, lib/assets, vendor/assets, or another configured path, Rails may not find it.

In JavaScript, Propshaft requires explicit handling for asset URLs. The Propshaft docs provide RAILS_ASSET_URL for transforming JavaScript asset references into digest-aware paths.

Example:

this.img = RAILS_ASSET_URL("/icons/trash.svg")

Fix 9: Check Docker, CI, and Deployment Build Steps

Asset issues often appear after deployment because the local machine has the files, but production does not.

For Docker builds, make sure assets are compiled inside the image:

RUN SECRET_KEY_BASE_DUMMY=1 bin/rails assets:precompile

Note: SECRET_KEY_BASE_DUMMY=1 requires Rails 7.1 or later. For older Rails versions, use a placeholder value instead: SECRET_KEY_BASE=placeholder1234567890abcdef... (must be at least 64 hex  characters).

Also check:

Are Node/Yarn/Bun dependencies installed?
Are npm packages available during build?
Are app/assets/builds files created?
Is public/assets copied into the final image?
Is RAILS_MASTER_KEY available if credentials are needed?

In multi-stage Docker builds, it is common to compile assets in one stage and accidentally not copy public/assets into the final runtime image.

For Heroku, Render, Fly.io, or similar platforms, check build logs. Confirm that assets:precompile runs and does not silently skip JavaScript or CSS bundling.

Fix 10: Clear CDN and Browser Cache

If files exist and the server is configured correctly, stale cache may still cause asset problems.

Check:

Is the CDN serving an old application.css path?
Did the deploy remove older compiled assets?
Are long cache headers applied to non-fingerprinted files?
Is the browser requesting an old digest filename?

Fingerprinting is designed to work well with long cache headers. But if a CDN caches HTML that references old asset paths, the browser may request files that no longer exist.

Fix this by clearing CDN cache and making sure HTML is not cached too aggressively.

Production Debugging Checklist

Use this checklist when Rails assets are not loading in production:

Did assets:precompile run successfully?
Does public/assets exist?
Is public/assets empty?
Is the manifest file present?
Are CSS and JS files fingerprinted?
Is RAILS_SERVE_STATIC_FILES enabled when needed?
Is Nginx or Apache serving the correct public directory?
Are Rails asset helpers used instead of hardcoded paths?
Are Sprockets manifest entries correct?
Are Propshaft asset paths correct?
Are excluded_paths configured with full paths?
Do app/assets/builds files exist?
Did jsbundling or cssbundling run?
Is Docker copying public/assets into the final image?
Is the CDN serving stale asset URLs?
Do production logs show AssetNotPrecompiled?

Quick Comparison: Rails Sprockets vs Rails Propshaft

Feature Sprockets Propshaft
Common in Older Rails apps Rails 8 new apps
Fingerprinting Yes Yes
Manifest mapping Yes Yes
Concatenation Yes No
Advanced preprocessing Yes, depending on setup Not the main purpose
Works with bundlers Yes Yes
Copies all load-path assets Not usually Yes
Best fit Legacy apps and older asset conventions Modern Rails apps with explicit build tools

Final Takeaway

Rails assets not loading in production usually means the asset pipeline did not complete one of its required production steps. The files may not have been precompiled, the manifest may be missing, the web server may not be serving public/assets, or the app may be using hardcoded asset paths instead of Rails helpers.

For Sprockets Rails apps, check manifest.js, precompile configuration, and AssetNotPrecompiled errors. For Rails Propshaft apps, check asset paths, excluded_paths, public/assets/.manifest.json, and the output from jsbundling or cssbundling.

Rails 8 Propshaft makes asset delivery simpler, but it also expects a clear separation between delivery and compilation. Once assets are compiled, fingerprinted, mapped, and served correctly, CSS, JavaScript, images, and fonts should load reliably in production.

Frequently Asked Questions

Why are Rails assets loading in development but not production?

Because development can serve assets directly, while production expects compiled, fingerprinted assets in public/assets, plus a valid manifest file.

What does assets:precompile do?

It prepares production assets by compiling or copying files, fingerprinting them, and generating a manifest that maps logical names to digest filenames.

Is Propshaft the default in Rails 8?

Yes. Rails 8 uses Propshaft by default for new applications.

Does Propshaft replace esbuild or Tailwind?

No. Propshaft handles asset delivery and fingerprinting. Build tools such as esbuild, Bun, Tailwind, Sass, and PostCSS still handle compilation when needed.

Why does Propshaft generate many digested files?

Propshaft copies assets from configured asset paths into public/assets. If raw source files are inside those paths, they may also be fingerprinted unless you exclude their directories.

Should I use config.assets.compile = true in production?

Usually no. It can help debug Sprockets issues, but the better production fix is to precompile assets properly during deployment.

Scroll to Top Icon