Migrating from Sprockets to Propshaft: Is It Worth It?

Rails 8 ships Propshaft by default, but migrating from Sprockets isn't always the right call. How to decide, and a step-by-step migration guide for when it is.

Ally Piechowski · · 10 min read
Migrating from Sprockets to Propshaft: Is It Worth It?

Rails 8 ships with Propshaft as the default asset pipeline for new applications. If you just upgraded an existing app to Rails 8, nothing changed. Propshaft is opt-in for existing apps, full stop.

The answer to whether migrating from Sprockets to Propshaft is worth it depends almost entirely on what your asset pipeline actually does today.

Table of Contents


What Propshaft Actually Is

Propshaft is deliberately minimal. It does four things:

  1. Digest stamping: adds a content hash to filenames for cache busting (application-abc123.css)
  2. Configurable load paths: register directories and reference assets uniformly
  3. Dev server: serves assets in development without precompilation
  4. Basic CSS URL rewriting: rewrites url() paths in CSS files to account for digest-stamped filenames

That’s it. Propshaft does not compile Sass. It does not transpile CoffeeScript. It does not process ERB in your asset files. It does not bundle JavaScript. It does not handle Sprockets manifest directives (//= require, //= require_tree).

The Propshaft README captures the spirit: JavaScript and CSS are either compiled by dedicated Node.js bundlers or served directly to the browser.

If you need transpilation or bundling, Propshaft assumes you’re handling that with a Node-based tool and it just needs to digest and serve the output. If that’s true for your app, the migration is straightforward.


Should You Switch?

Probably not, if your current setup is stable.

A Propshaft maintainer said it plainly in a Rails discussion thread: “If your existing project and workflow works fine with Sprockets? It’s fine to stay on it.” It’s still actively maintained, and there is no deprecation timeline.

The case for switching is simplicity, not fixing a problem you have today. Propshaft is faster, has a smaller footprint, and produces more predictable behavior in exchange for doing less. If your asset pipeline is already the source of confusion or friction, the migration can help. If it’s invisible and working, this is just churn.


When Propshaft Is the Right Call

You’re using Import Maps with vanilla CSS. This is the sweet spot Propshaft was built for. Import Maps handle JS module resolution in the browser, so there’s nothing to bundle. If you’re writing plain CSS without Sass, Propshaft digests and serves it with zero friction. The migration is mostly just removing Sprockets.

You’re already using a Node bundler for JS and CSS. If you’ve got jsbundling-rails and cssbundling-rails, Sprockets is just sitting between your bundler output and the browser. Most of the migrations I’ve done for clients fall into this category: Sprockets was vestigial and removing it simplified the deploy pipeline immediately.

You’re setting up Kamal or containerized deploys and want a simpler asset step. Propshaft’s precompile step is fast and produces straightforward output.

New Rails apps get Propshaft. Community tooling, gems, and documentation will increasingly assume it.


When to Stay on Sprockets

You’re using Sass through Sprockets without a Node build step. If sass-rails is handling your Sass compilation and you haven’t set up cssbundling-rails, you’ll need to wire up a Node-based Sass pipeline before you can switch. That’s a real project on its own, and probably not worth doing just to enable a Propshaft migration.

You have ERB in your asset files. Propshaft doesn’t process ERB. If you’re doing things like <%= asset_path 'image.png' %> inside CSS or JS files, you’ll need to rethink that before you can switch.

You have gems that depend on Sprockets. This is the big one. Some gems register assets through Sprockets internals. ActiveAdmin is the most common: it ships its own CSS and JS as Sprockets engine assets and requires Sprockets to be present. If a gem’s documentation says it “requires the Sprockets asset pipeline,” treat that as a hard blocker, not a warning to work around.

Run grep sprockets Gemfile.lock to see every gem in your dependency tree that pulls it in. Then check config/initializers/assets.rb for any lines like:

Rails.application.config.assets.precompile += ["admin.css", "admin.js"]

Those are Sprockets-specific. In Propshaft, there’s no precompile list; everything in the load path is served. You’ll need to either move those files into a path Propshaft already watches, or add the path explicitly via config.assets.paths.

If you’ve got years of custom initializers, asset loading logic, and preprocessor configuration that works (and nobody on the team really understands all of it), migrating is a high-risk low-reward project. I’ve seen teams spend a full sprint migrating a stable Sprockets setup for no measurable benefit. If the quarter is full, this waits.


How to Migrate from Sprockets to Propshaft

This assumes you’re starting from a Rails 7+ app on Sprockets. If you’re on Rails 6 or earlier, upgrade Rails first.

Step 1: Handle Sass and transpiled assets

Before you remove Sprockets, you need to make sure any transpilation it was doing is handled elsewhere. If you’re already using jsbundling-rails and cssbundling-rails, skip ahead.

If you use Sass:

./bin/rails css:install:sass

This installs cssbundling-rails with Dart Sass, adds a build:css script to your package.json, and updates Procfile.dev so bin/dev watches and rebuilds on change.

The generated build command targets your entry point file directly:

"build:css": "sass ./app/assets/stylesheets/application.scss:./app/assets/builds/application.css --no-source-map --load-path=node_modules"

Source files live in app/assets/stylesheets/, compiled output lands in app/assets/builds/. Make sure app/assets/builds/* is in your .gitignore. Rails adds it for new apps but it’s a common gap during manual migrations.

If you have multiple stylesheet entry points, list each input/output pair explicitly:

"build:css": "sass ./app/assets/stylesheets/application.scss:./app/assets/builds/application.css ./app/assets/stylesheets/admin.scss:./app/assets/builds/admin.css --no-source-map --load-path=node_modules"

If you use CoffeeScript: There’s no clean path. Your options are to rewrite the CoffeeScript as plain JS, or stay on Sprockets. Most teams choose to rewrite. CoffeeScript is effectively unmaintained.

Step 2: Swap the gems

# Remove from Gemfile:
- gem "sprockets"
- gem "sprockets-rails"
- gem "sass-rails"

# Add to Gemfile:
+ gem "propshaft"

Run:

bundle install

Check bundle info sprockets. If any other gem still lists it as a dependency, that gem may break. Common culprits: ActiveAdmin, Administrate, older versions of devise-bootstrap-views.

Step 3: Remove Sprockets configuration files

Before deleting anything, open config/initializers/assets.rb if it exists and check for lines like:

Rails.application.config.assets.precompile += ["admin.css", "admin.js"]

Note every file listed; you’ll need to include them explicitly in your layout (Step 6). Then delete the file:

rm config/initializers/assets.rb   # if it exists
rm app/assets/config/manifest.js   # the Sprockets manifest

In config/application.rb, remove any lines like:

config.assets.paths << Rails.root.join('app', 'assets')

Propshaft auto-loads subdirectories of app/assets, lib/assets, and vendor/assets. Files placed directly in app/assets/ itself won’t be found; they need to be in a subdirectory. Only add to config.assets.paths for paths outside these standard trees.

If you have require "sprockets/railtie" anywhere, remove it.

Step 4: Remove Sprockets directives

Sprockets manifest syntax (//= directives) is meaningless to Propshaft. Find them:

grep -rn "//= require" app/assets/

Assets are served from the load path directly. Replace //= require_tree with native ES module imports or Import Map <script> tags. Replace //= directives in CSS with native @import statements.

Before (Sprockets):

// app/assets/javascripts/application.js
//= require rails-ujs
//= require_tree .

After (Propshaft + Import Maps):

<!-- handled via importmap-rails config/importmap.rb -->

Before (Sprockets):

/* app/assets/stylesheets/application.css */
/*
 *= require_tree .
 *= require_self
 */

After (Propshaft + plain CSS):

/* app/assets/stylesheets/application.css */
@import "variables.css";
@import "components.css";
@import "layout.css";

After (Propshaft + Sass from Step 1, rename the file to .scss):

// app/assets/stylesheets/application.scss
@import "variables";
@import "components";
@import "layout";

Step 5: Replace Sprockets CSS helpers with standard url()

Sprockets provides helper functions like image-url() and asset-url() for use inside CSS files. Propshaft doesn’t. Instead, it rewrites standard url() calls. The path you pass is the asset’s logical path, relative to the load path root, not the filesystem.

Before:

background: image-url('hero.jpg');

After:

background: url('/hero.jpg');

If your image is at app/assets/images/backgrounds/hero.jpg, reference it as url('/backgrounds/hero.jpg').

Two common mistakes:

If you’re unsure of a file’s logical path, bin/rails assets:reveal is the source of truth.

Rails view helpers (image_tag, asset_path, asset_url) work unchanged.

Step 6: Update your layout

For most apps, stylesheet_link_tag barely changes. "application" now refers to the compiled file Propshaft serves directly instead of a Sprockets manifest:

<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>

Files from config.assets.precompile. If you found any extra files listed in config/initializers/assets.rb back in Step 3, those need to be included in your layout explicitly. Propshaft has no precompile list (everything in the load path is served), but you still need to link them:

<%= stylesheet_link_tag "application", "admin", "checkout", "data-turbo-track": "reload" %>

A note on :all and :app. :all includes every CSS file in the entire load path (including gems). :app includes only files from app/assets, the right default for most migrated apps:

<%= stylesheet_link_tag :app, "data-turbo-track": "reload" %>

Use explicit names only when you need to control load order or include files from outside app/assets.

Step 7: Audit ActionText and third-party gem assets

After switching to Propshaft, Trix editor styles from ActionText may stop loading. Check whether you’re using action_text/style or similar:

grep -rn "action_text" app/views/ app/assets/

With Propshaft, ActionText assets are accessible from the load path, but they won’t load automatically. If Trix styles are missing after the migration, add these imports to your stylesheet:

@import url('/actiontext.css');
@import url('/trix.css');

If you’re on an older ActionText version, update rails first.

If you need to read a gem asset’s content directly in Ruby (common for inline SVGs), use:

Rails.application.assets.load_path.find('logo.svg').content.html_safe

Step 8: Verify in development

Start your dev server:

bin/dev

Check that:

If something you expect is missing, run bin/rails assets:reveal, which lists every file and logical path Propshaft can see. If a file doesn’t appear there, its directory isn’t in the load path; add it via config.assets.paths.


Common Propshaft Migration Gotchas

Running assets:precompile in development kills dynamic resolution. Propshaft decides between static and dynamic resolution at boot time based on whether public/assets/.manifest.json exists. Running assets:precompile writes that manifest, and on the next boot Propshaft switches to static mode, serving only what’s in the manifest and ignoring any source changes. Fix it:

bin/rails assets:clobber

This deletes public/assets/ including the manifest.

Sass partial files compiled as entry points. Dart Sass compiles any non-underscore .scss file it finds as an entry point. If you have partials not prefixed with _, rename them (_variables.scss). Don’t reach for a directory restructure.

Having both Propshaft and Sprockets active will cause conflicts. Treat them as mutually exclusive. Remove Sprockets fully before adding Propshaft, including transitive gem dependencies.

Gems that register assets through Sprockets. Some gems use initializer blocks that call config.assets.paths or register with Sprockets::Railtie. These will silently fail or raise after switching. Check the gem’s source or issues page for Propshaft compatibility.

javascript_include_tag vs Import Maps. The tag still works. What changed is where the file comes from. If it was a Sprockets-bundled file, it now needs to be served directly by Propshaft (pre-compiled by esbuild or similar) or loaded via Import Maps.


Quick Reference

Feature Sprockets Propshaft
JS compilation CoffeeScript, Babel (via gems) None: external bundler only
CSS compilation Sass, Less (via gems) None: external bundler only
ERB in assets Yes (.css.erb, .js.erb) No
Fingerprinting Manifest-based digest hashing Load-path based digest hashing
Manifests manifest.js directives None: everything in load path is served automatically
CSS URL helpers image-url('path') Standard url('path')
Precompile list config.assets.precompile No list: load path is the list

TL;DR

Don’t migrate because Rails 8 ships Propshaft by default. Migrate because your app would genuinely benefit from a simpler pipeline. Those are different reasons, and only the second one is a good one.

If you’re weighing whether this fits into your current upgrade work, get in touch.


Related Articles