RailsVite
Vite integration for Rails, inspired by Laravel's Vite plugin. No proxy, no config duplication, no magic.
Table of Contents
- How It Works
- Quick Start
- Usage
- Vite Config
- Adding Frameworks
- SSR
- Auto Build
- Testing the Build
- Custom Paths
- Rake Tasks
- Migrating from vite_rails
- Contributing
- License
How It Works
Development: The Vite plugin writes tmp/rails-vite.json with the dev server URL. The Rails helper reads it and emits <script> tags pointing directly at Vite. The browser talks to Vite — Puma never touches your assets.
Production: vite build outputs fingerprinted assets to public/vite/ with a standard Vite manifest. The Rails helper reads the manifest and emits the correct tags.
No Rack proxy. No config/vite.json. No extra binstubs.
Quick Start
Add to your Gemfile:
gem "rails_vite"Run the install generator:
bundle install
bin/rails generate rails_vite:installThis creates vite.config.ts, installs dependencies, and updates your layout.
Start development:
bin/devUsage
In your layout:
<%= vite_tags "application.js" %>Short names are automatically prefixed with sourceDir (default: app/javascript). Paths containing / are used as-is.
Development output (when tmp/rails-vite.json exists):
<script src="http://localhost:5173/@vite/client" type="module"></script>
<script src="http://localhost:5173/app/javascript/application.js" type="module"></script>Production output (reads manifest):
<link rel="modulepreload" href="/vite/assets/vendor-b3c4d5e6.js" />
<script src="/vite/assets/application-a1b2c3d4.js" type="module"></script>
<link rel="stylesheet" href="/vite/assets/application-x9y8z7w6.css" />Helpers
| Helper | Purpose |
|---|---|
vite_tags(*entries, **options) |
Emits script, stylesheet, and modulepreload tags |
vite_javascript_tag(*entries, **options) |
Same as vite_tags, appends .js to extensionless names |
vite_stylesheet_tag(*entries, **options) |
Same as vite_tags, appends .css to extensionless names |
vite_typescript_tag(*entries, **options) |
Same as vite_tags, appends .ts to extensionless names |
vite_asset_path(name) |
Returns the fingerprinted path from the manifest |
vite_image_tag(name, **options) |
Image tag with manifest-resolved src |
All tag helpers accept arbitrary HTML attributes:
<%= vite_tags "application.js", "application.css",
"data-turbo-track": "reload", nonce: content_security_policy_nonce %>CSS Entry Points
CSS files are detected by extension and emit <link rel="stylesheet">:
<%= vite_tags "application.css" %>CSP Nonces
<%= vite_tags "application.js", nonce: content_security_policy_nonce %>Subresource Integrity (SRI)
SRI lets browsers verify that fetched assets haven't been tampered with by checking cryptographic hashes. Install the vite-plugin-manifest-sri plugin:
npm install -D vite-plugin-manifest-sriimport { defineConfig } from 'vite';
import rails from 'rails-vite-plugin';
import manifestSRI from 'vite-plugin-manifest-sri';
export default defineConfig({
plugins: [
rails(),
manifestSRI(),
],
});That's it — integrity and crossorigin="anonymous" attributes are automatically added to all script, stylesheet, and modulepreload tags when the manifest includes integrity hashes.
Asset Discovery (Images, Fonts)
Use import.meta.glob in your entry point to include assets in the Vite manifest:
// app/javascript/application.js
import.meta.glob(['../assets/images/**']);Then reference them in views:
<%= vite_image_tag "app/assets/images/logo.png", alt: "Logo" %>Vite Config
The install generator creates a minimal vite.config.ts:
import { defineConfig } from 'vite';
import rails from 'rails-vite-plugin';
export default defineConfig({
plugins: [
rails(),
],
});Plugin Options
| Option | Default | Description |
|---|---|---|
input |
auto-detected | Entry point(s). If sourceDir/entrypoints/ exists, all files in it are used. Otherwise, detects application.{js,ts,jsx,tsx} in sourceDir
|
sourceDir |
'app/javascript' |
Source directory. Short names are prefixed with this. Also sets the @ import alias |
ssr |
— | SSR entry point |
ssrOutputDirectory |
'ssr' |
SSR output directory |
devMetaFile |
'tmp/rails-vite.json' |
Dev metadata file path |
buildDirectory |
'vite' |
Build output subdirectory inside public/
|
publicDirectory |
'public' |
Public directory |
refresh |
true |
Paths to watch for full-page reload. true watches app/views/** and app/helpers/**
|
Multiple Entry Points
rails({
input: ['application.js', 'admin.js'],
})<!-- In application layout -->
<%= vite_tags "application.js" %>
<!-- In admin layout -->
<%= vite_tags "admin.js" %>Custom Source Directory
rails({
input: ['entrypoints/application.ts', 'entrypoints/admin.ts'],
sourceDir: 'app/frontend',
})<%= vite_tags "entrypoints/application.ts" %>Adding Frameworks
React
npm install -D @vitejs/plugin-reactimport { defineConfig } from 'vite';
import rails from 'rails-vite-plugin';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [
react(),
rails(),
],
});The React Refresh preamble is injected automatically when @vitejs/plugin-react is detected — no manual setup needed.
Vue
npm install -D @vitejs/plugin-vueimport { defineConfig } from 'vite';
import rails from 'rails-vite-plugin';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [
vue(),
rails(),
],
});SSR
Set ssr to the entry point used for server-side rendering. When you run npx vite build --ssr, the plugin uses this as the input and outputs to the ssrOutputDirectory (default: ssr/).
rails({
ssr: 'ssr.tsx',
})Build and run:
npx vite build && npx vite build --ssr
node ssr/ssr.jsAuto Build
When the Vite dev server is not running, rails_vite automatically rebuilds assets on the first request if sources have changed. This is useful for system tests and quick checks without running bin/dev.
Disable it:
# config/initializers/rails_vite.rb
Rails.application.config.rails_vite.auto_build = falseBy default, auto build is enabled in development and test (Rails.env.local?).
Note: for parallel test runners, disable auto build and use rake vite:build before the suite instead.
Testing the Build
To verify your production build works in development:
rake vite:build # build assets
bin/rails s # start Rails without Vite dev serverWithout the Vite dev server running (no tmp/rails-vite.json), Rails serves built assets from public/vite/. To switch back to dev mode, start Vite again — the dev metadata takes priority.
Clean up built assets with rake vite:clobber.
Custom Paths
If you override build.outDir in vite.config.ts, tell the gem where to find things:
# config/initializers/rails_vite.rb
Rails.application.config.rails_vite.manifest_path = Rails.root.join("public/custom/manifest.json")
Rails.application.config.rails_vite.asset_prefix = "/custom"Defaults match the plugin defaults — no config needed if you follow conventions.
Rake Tasks
| Task | Description |
|---|---|
rake vite:build |
Build assets for production |
rake vite:install |
Install JavaScript dependencies |
rake vite:clobber |
Remove public/vite/
|
vite:build hooks into assets:precompile and test:prepare automatically. Skip with SKIP_VITE_BUILD=1.
Migrating from vite_rails
1. Swap dependencies
# Gemfile
- gem "vite_rails"
+ gem "rails_vite"// package.json — replace vite-plugin-ruby with rails-vite-plugin
- "vite-plugin-ruby": "^5.1.1"
+ "rails-vite-plugin": "^0.1.0"2. Replace vite.config.ts
import { defineConfig } from 'vite';
import rails from 'rails-vite-plugin';
export default defineConfig({
plugins: [
rails({
sourceDir: 'app/frontend',
}),
],
});If you have an entrypoints/ directory inside sourceDir, all files in it are auto-discovered — no need to list them. Otherwise, set input explicitly.
3. Delete files
-
config/vite.json— settings now live invite.config.ts -
bin/vite— no longer needed,Procfile.devrunsnpx vitedirectly
4. Update layouts
Remove vite_client_tag and vite_react_refresh_tag — both are automatic now.
The vite_javascript_tag, vite_stylesheet_tag, and vite_typescript_tag helpers work as drop-in replacements:
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/skryukov/rails_vite.
License
The gem is available as open source under the terms of the MIT License.