Svelte on Rails

Combining two beautiful worlds: Rails backend power and Svelte frontend joy.

For most interfaces, Hotwire is already a great choice. But when you need richer frontend interactivity, Svelte can be the perfect lightweight layer on top of your Rails app.

There are two important hurdles to understand when using Svelte in a Rails environment.

First hurdle: translations and other Rails-side data must be passed into Svelte components through props. To simplify this, the to_svelte helper is provided.

Second hurdle: Svelte components must be compatible with server-side rendering.

This means you cannot use browser-only APIs such as window or document in code that runs during server-side rendering. The same applies to directly imported modules that use those APIs. If browser-only code runs later, for example after a button click, that is usually fine.

A simple test while developing a component is to reload the page. If you do not see a blinking error message, you are good to go.

Because of this, building a Svelte component in a Hotwire-based Rails environment is not quite as simple as building one in SvelteKit.

The advantage is that you can keep delivering a classic, backend-powered Rails app — with backend-driven tests — while adding rich interactivity exactly where you need it, beyond what Hotwire can provide.

Installation + optional Showcase

Copy, paste, and run the following commands in your terminal:

1. Create a new Rails app

If you start from scratch:

rails new svelte-on-rails-testapp app --skip-javascript && cd svelte-on-rails-testapp

2. Install svelte-on-rails

bundle add svelte-on-rails
rails g svelte_on_rails:install

The installer is designed to be carefully and safe:

  • it checks prerequisites
  • it uses an isolated sandbox for checking npm dependencies before applying changes to the app.
  • it won’t overwrite existing files without asking

Just follow the prompts.

3. Install and run the Showcase

rails g svelte_on_rails:showcase

Additional: On Models, where you want to use the to_svelte helper, add exposes_to_svelte, for example:

class Book < ApplicationRecord
  exposes_to_svelte
end

Code Example

View helper:

<%= svelte('components/Stats', user: @user.to_svelte(:login_count, :name) %>

Component:

app/frontend/javascript/components/User.svelte:

<script>
  let { user } = $props();
</script>

<h1>{ user._schema.self.name } { user.name } { user._schema.login_count.label }: { user.login_count }</h1>

Output:

<h1>Benutzer Josef Logins: 77</h1>

<!-- 
the #to_svelte helper 
• returns useful meta-data for models and columns
• serializes data
-->

On the server logs, with the «Completed Line»:

17:35:52 web.1  |   [SOR] ...
17:35:52 web.1  |   [SOR] Stats.svelte returned from cache (1.4ms)
17:35:52 web.1  | Completed 200 OK in 43ms ( ... | ... | Svelte: 2 cached, 1 rendered, 3.3ms)
# or
12:36:45 web.1  | Completed 200 OK in 831ms ( ... | ... | SVELTE BUILD FAILED!)
# or
12:36:45 web.1  | Completed 200 OK in 831ms ( ... | ... | Svelte: build, 3 warnings, 3 cached, 786.7ms)
# or
12:35:22 web.1  | Completed 200 OK in 44ms ( ... | ... | Svelte: 3 cached, 2.1ms)

When you see something like «SVELTE BUILD FAILED» you will find a detailed error message with the links to the files.

Before You Build Your First App

  • Check out the Hotwire Svelte Helpers Example App:
    • This set of helpers makes it easy to create dropdowns, tooltips, and modals with Svelte and Stimulus.
    • It includes form helpers for Svelte that interact with the Rails backend.
    • Z-index issues are solved by configurable rendering into a root-level container.
  • Consider using Svelte components within app/views, just like partials:
  • Testing:
    • For testing we use this Playwright setup.
    • Important: The gem adds the class hydrated to every component after it has finished hydrating. Append it to your selectors so tests don’t interact with a component before its event listeners are attached, for example:
      page.wait_for_selector(".my-component.hydrated .any-button").click
      
    • This aligns to the .stimulus-connected class of @csedl/hotwire-svelte-helpers.
    • Important: The hydrated class is added to the root element of the Svelte component. This is different from the svelte:options tag option,
  • Performance:
    • Add the redis gem to your Gemfile and set use_caching: true in config/svelte_on_rails.yml.
    • Use watch_changes: true in development only.
    • Check uncached props.

Why?

Svelte`s reactivity is the missing piece for Rails+Hotwired.

Why was Svelte invented? Watch the Rethinking Reactivity video (especially 5:00–7:10).

Features

  • Server-Side Rendering
  • SSR Server (Default / similar to react_on_rails pro)
  • Fine-grained fragment caching
  • ActiveRecord integration – pass models directly with .to_svelte
  • optional support for placing .svelte components directly in app/views
  • Plays nicely with Hotwire Turbo (option: SSR only for initial page load)
  • Fully integrated with rails assets:precompile
  • Built-in interface for server → client communication via ActionCable / Turbo Streams
  • Fully configurable
  • Detailled logging for performance optimization and error detection

Enjoy it! 😉

For Questions or Issues, please open an issue on GitLab.

Best Regards,

Chris