The go-to resource for upgrading Ruby, Rails, and your dependencies.

Moving from Sprockets to a Modern Frontend in Ruby on Rails


For nearly a decade, Sprockets served as the default asset pipeline for Ruby on Rails, concatenating and minifying JavaScript and CSS with remarkable reliability. Over time, however, the frontend ecosystem shifted toward complex dependency graphs, Node.js modules, and sophisticated build tools. When engineering teams attempt a Ruby and Rails Upgrade for large and complex applications, they frequently discover that their legacy Sprockets configuration has become a significant source of technical debt.

Migrating to a modern frontend build pipeline is often a prerequisite for a successful migration to the new version of Rails. This guide details a battle-tested workflow for replacing Sprockets with contemporary alternatives.

Key takeaways for engineering leaders:

  • Multiple Upgrade Paths: Rails now supports Import Maps, CSS/JS Bundling (via Esbuild, Rollup, or Webpack), and community-driven solutions like Vite Ruby.
  • Technical Debt Remediation: A pipeline migration forces a comprehensive audit of outdated frontend dependencies, improving overall technical health.
  • Performance Gains: Modern bundlers offer significantly faster compilation times and Hot Module Replacement (HMR) for a superior developer experience.
  • Structural Changes: Assets traditionally housed in app/assets must be reorganized to align with modern module resolution strategies.

The Historical Context of Asset Management

Originally, Sprockets was designed to solve a specific problem: serving multiple JavaScript and CSS files efficiently without requiring external build tools. It relied on a system of directive comments — such as //= require jquery — to stitch files together into a single, monolithic payload.

Sprockets, strictly speaking, does not understand JavaScript modules or CSS scopes; it operates primarily on file concatenation and regular expressions. This approach worked perfectly when frontend logic consisted of discrete jQuery plugins and global variables.

As applications grew, developers increasingly relied on npm packages. Webpacker was introduced as a bridge to Webpack, but its heavy abstraction layer often caused configuration headaches. Eventually, Rails pivoted again, offering multiple distinct pathways for asset management in Rails 7. When you upgrade a Rails app today, you must choose the pipeline that best fits your team’s operational capacity and frontend complexity.

Evaluating Modern Frontend Alternatives

Before we get into the mechanical steps of migration, we need to examine the available options. There are three major approaches to modernizing your frontend pipeline.

The first is Import Maps, the default in Rails 7. This approach relies on native browser support for ES modules. It requires no Node.js installation, no compilation step, and no complex configuration. You pin remote modules directly in an config/importmap.rb file. Generally speaking, Import Maps are ideal for applications with light to moderate JavaScript needs.

The second option is jsbundling-rails and cssbundling-rails. These gems integrate Node-based bundlers — like Esbuild for JavaScript and Tailwind or Dart Sass for CSS — directly into the Rails asset pipeline. This is my preferred method for teams that require npm packages or TypeScript, as Esbuild compiles assets in milliseconds rather than the tens of seconds required by older Webpack configurations.

The third option is Vite Ruby. Vite leverages native ES modules during development for instant Hot Module Replacement, then bundles assets using Rollup for production. Vite Ruby provides tight integration with Rails helpers and is excellent for heavy JavaScript applications, particularly those utilizing frameworks like React, Vue, or Svelte.

Preparing Your Codebase for Migration

Before executing the migration, you should conduct a thorough audit of your existing assets. Moving thousands of lines of untracked, global JavaScript into a modern module system often exposes hidden dependencies and race conditions.

First, identify all external libraries managed by Sprockets or outdated package managers like Bower. You will need to determine if these libraries are available on npm or if they need to be vendor-managed.

Next, review your app/assets/javascripts/application.js manifest. A typical legacy manifest looks like this:

//= require rails-ujs
//= require jquery
//= require bootstrap
//= require tree .

The require_tree . directive is particularly dangerous during a migration, as it loads files in an unpredictable alphabetical order. Modern bundlers require explicit import statements, forcing you to define the exact execution order of your dependencies.

Executing the Migration with jsbundling-rails

For this guide, we will demonstrate a migration using jsbundling-rails with Esbuild, as it provides a robust, predictable build process suitable for large and complex applications.

Step 1: Installation

Begin by adding the necessary gems to your Gemfile.

gem "jsbundling-rails"
gem "cssbundling-rails"

Next, run the installation tasks. These commands will generate the required Node.js configuration files, install Esbuild, and create entry point files in app/javascript.

$ bundle install
$ rails javascript:install:esbuild
$ rails css:install:bootstrap

You also may notice that these commands create a Procfile.dev. Since Esbuild requires a separate process to watch and compile files during development, you will now boot your application using Foreman or the bin/dev script rather than rails server.

Step 2: Reorganizing Assets

With the build tools installed, we must relocate our assets. Historically, all JavaScript lived in app/assets/javascripts. Now, we move our application logic into app/javascript.

$ mv app/assets/javascripts/* app/javascript/

We must then update our entry point. Open app/javascript/application.js and replace the old Sprockets directives with modern ES module imports.

// Before (Sprockets)
// //= require rails-ujs
// //= require_tree ./components

// After (ES Modules)
import Rails from "@rails/ujs"
import "./components/dropdown"
import "./components/modal"

Rails.start()

Notice that we must explicitly import dropdown and modal. This explicit dependency graph ensures that code only executes when its prerequisites are met.

Step 3: Updating Layout Tags

Finally, we need to instruct Rails to serve the newly bundled assets rather than the old Sprockets payload. Open your primary layout file, typically app/views/layouts/application.html.erb.

Replace the legacy javascript_include_tag with the modern equivalent, ensuring you include the defer attribute.

<!-- Remove this -->
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>

<!-- Add this -->
<%= javascript_include_tag 'application', 'data-turbo-track': 'reload', defer: true %>

The defer attribute is critical. Modern bundles are often loaded in the <head> of the document; without defer, the browser will pause HTML parsing to download and execute the JavaScript, resulting in significant performance degradation.

Handling Edge Cases: Images and Fonts

One may wonder: if we remove Sprockets entirely, how do we manage static assets like images and fonts? The answer is straightforward, though it requires a shift in workflow.

If you are using jsbundling-rails, Sprockets (or Propshaft, its modern, stripped-down successor) still handles images and fonts. You can leave these files in app/assets/images and app/assets/fonts and reference them using standard Rails helpers like image_tag.

However, if your CSS — now compiled by Esbuild or Dart Sass — needs to reference an image, you cannot rely on the old image-url() Sass helper provided by sass-rails. Instead, you must use relative paths or configure your bundler to rewrite asset URLs during the build process.

Post-Upgrade Support and Maintenance

A successful migration to the new version of your frontend pipeline is a significant milestone, but it requires ongoing maintenance. The Node.js ecosystem moves quickly; dependencies that are secure today may contain vulnerabilities tomorrow.

As part of your post-upgrade support strategy, you should implement automated dependency scanning for your package.json, much like you use Bundler Audit for your Gemfile.lock. Regularly running npm audit or yarn audit in your Continuous Integration (CI) pipeline ensures that your frontend technical health remains strong.

Of course, migrating off Sprockets is rarely a weekend project for a mature application. It requires careful planning, rigorous testing, and a deep understanding of module resolution. By treating this migration as a critical architectural upgrade rather than a minor technical chore, engineering teams can secure their application’s performance and maintainability for years to come.

Sponsored by Durable Programming

Need help maintaining or upgrading your Ruby on Rails application? Durable Programming specializes in keeping Rails apps secure, performant, and up-to-date.

Hire Durable Programming