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

Upgrading Legacy Rails Views to Svelte Components Using Inertia.js


When naval architects transitioned from sail to steam power, they did not immediately discard the rigging. For decades, ships featured both technologies — a hybrid approach that ensured reliability while adopting the new propulsion system. Software architecture requires a similar pragmatic transition.

When we execute a Ruby and Rails upgrade, we frequently face a significant challenge on the frontend. Legacy Rails applications often rely heavily on complex Action View templates, intermingled with jQuery or scattered Stimulus controllers. As user interface requirements become more interactive, this traditional approach often leads to technical debt and slow rendering times, particularly affecting performance metrics like Largest Contentful Paint (LCP).

We need a way to accomplish modern JavaScript integration without abandoning the robust routing and controller logic of Rails. This is where Inertia.js proves invaluable, acting as a bridge that allows us to render Svelte components directly from Rails controllers.

Why Svelte and Inertia.js?

Before examining the implementation, we must understand the architectural choice and the alternatives available.

Svelte offers a distinct advantage for legacy modernization. Unlike React or Vue, which ship a virtual DOM to the browser, Svelte is a compiler. It transforms components into highly optimized, vanilla JavaScript at build time. This results in smaller bundle sizes and faster execution, which directly reduces LCP times and improves overall application performance.

Inertia.js replaces the traditional API-driven Single Page Application (SPA) architecture. Instead of building a separate frontend application that communicates with a Rails API via JSON, Inertia allows Rails to remain the definitive source of routing and state. When a user navigates, Rails processes the request and returns a lightweight JSON response containing the data needed for the specific Svelte component, rather than rendering a full HTML layout.

This approach offers several benefits for technical debt remediation:

  • It eliminates the need for a separate GraphQL or REST API for frontend navigation.
  • It preserves the existing Rails authentication and authorization mechanisms.
  • It allows for an incremental migration. We can upgrade one view at a time while the rest of the application remains standard server-rendered HTML.

While Hotwire (Turbo and Stimulus) is the default Rails solution for interactivity, it can become cumbersome when dealing with highly complex, state-heavy user interfaces. Inertia and Svelte provide a structured component model that is often more maintainable for these specific, highly interactive views. We can utilize Hotwire for standard CRUD operations and selectively deploy Svelte for sophisticated interactive elements.

Implementing the Integration

The transition requires configuring both the Ruby backend and the JavaScript frontend to establish communication.

Configuring the Rails Environment

First, we need to add the Inertia.js adapter to our application.

```ruby

Gemfile

gem ‘inertia_rails’ ```

After running bundle install, we configure the middleware. This middleware intercepts requests from the Inertia frontend and ensures Rails responds with the correct JSON format instead of rendering an ERB template.

We then create a shared layout component on the frontend, which Inertia will use to wrap our individual page components. This mimics the behavior of app/views/layouts/application.html.erb.

In our controllers, the change requires minimal code modification. Instead of implicitly rendering a view or calling render json:, we use the render inertia: directive.

```ruby class DashboardsController < ApplicationController def show @metrics = DashboardMetrics.calculate(current_user)

render inertia: 'Dashboard/Show', props: {
  user: current_user.as_json(only: [:id, :name, :email]),
  metrics: @metrics
}

end end ```

Notice that we explicitly serialize the data. We should always control exactly what data is sent to the client to avoid unintentional data exposure, as the entire props object becomes readable in the browser’s memory.

Setting Up Svelte and Vite

To compile Svelte components, we typically rely on Vite, managed through the vite_rails gem. This modern build tool offers significantly faster compilation times compared to older solutions like Webpacker.

We configure Vite to process Svelte files and set up the Inertia client initialization.

```javascript // frontend/entrypoints/application.js import { createInertiaApp } from ‘@inertiajs/svelte’

createInertiaApp({ resolve: name => { const pages = import.meta.glob(’../pages/**/*.svelte’, { eager: true }) return pages[../pages/${name}.svelte] }, setup({ el, App, props }) { new App({ target: el, props }) }, }) ```

This configuration instructs Inertia on how to resolve the component name provided by the Rails controller (e.g., 'Dashboard/Show') to the actual Svelte file on the filesystem.

The Incremental Migration Strategy

A massive, immediate rewrite of the frontend rarely succeeds. Instead, we use an incremental approach.

Identifying High-Value Views

We should target views where Svelte provides the most immediate value. These are typically:

  • Pages with complex, cascading state changes.
  • Forms with intricate validation logic.
  • Dashboards requiring frequent, partial updates.

Static pages, such as an “About Us” section or privacy policies, should remain as standard ERB templates. Inertia accommodates mixing server-rendered views with Svelte-rendered views seamlessly within the same application.

The Feature Flag Approach for Views

For critical paths, we can implement a parallel strategy. We create the new Svelte component and route a small percentage of traffic through the upgraded logic using feature flags. This allows us to monitor the system’s technical health, specifically verifying that the new component functions correctly under production load before committing entirely.

If errors occur, we disable the flag to restore the previous state immediately, achieving a safer, zero-downtime deployment.

Long-Term Maintainability

Adopting Svelte and Inertia.js fundamentally changes how we organize frontend code.

We must establish strict conventions for component structure. We typically organize components into pages (which correspond directly to Rails actions) and components (which are reusable UI elements).

Furthermore, we must be diligent about serialization. Rails allows developers to pass full Active Record objects to views with minimal friction, but with Inertia, this data is serialized to JSON and sent to the client network layer. We must use tools like Active Model Serializers, Jbuilder, or explicit as_json calls to ensure we do not leak sensitive information.

This hybrid architecture provides a pragmatic path forward. We retain the mature ecosystem of Ruby on Rails for our backend logic while adopting a modern, highly performant component framework for our user interfaces. It is a measured approach to technical debt remediation that respects both the constraints of existing systems and the demands of modern web development.

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