Integrating Vite and Vue With Rails 7
Integrating Vite and Vue With Rails 7
Why Vite?
Vite is a frontend build tool and development server which serves two important functions in the modern web development lifecycle.
As a development server, it serves all of your JavaScript modules to the frontend during development, meaning that you can easily write JavaScript code to run in the browser and use all of the modern JavaScript libraries and packages that power the internet. It implements Hot Module Replacement (HMR) which means that when you make a change, it loads immediately so you can see your changes in real time rather than waiting for everything to compile again.
As a frontend build tool, it bundles all of your frontend code and assets and optimizes them to reduce frontend load times and provide a better user experience. These two functions are essential for modern web development.
There are other tools that perform these same functions, so why would you use Vite over the other options?
Webpack is one of the main alternatives, and it was the default frontend build tool for Rails 5 and 6. Webpack has a number of issues, with the main one being the amount of time it takes to compile. Check out this article to see a comparison of Vite and Webpack bundle sizes and build times. In my opinion, Vite also has better documentation and is simpler to use than Webpack.
In Rails 7, the default is Import Maps which is simple to use, but does not perform all the same functions as Webpack or Vite. Instead of bundling, Import Maps loads each JavaScript module independently. While this approach is simple and can work well for small applications, it may lead to excessive HTTP requests as each module is requested separately either from the backend or from a CDN. Additionally, component libraries such as Vuetify that rely on both JavaScript and CSS may not work seamlessly using Import Maps.
Example Rails/Vite/Vue App
Creating the Application
The rest of this article will walk you through how to integrate Vite and Vue in a Rails application. It assumes that you already have Ruby and Rails installed and have a basic familiarity with Rails, including how to use a Gemfile and set up routes and controllers. For this example, I am using Rails 7.0.8.4 and Ruby 3.1.3; I would recommend using the same versions to make sure you can follow along.
The example app will use Rails as the backend and Vue as the frontend to display deep field photos from the Hubble and James Webb telescopes, so I will call it deep_field_photos
. To create the new app without the default JavaScript and Import Maps setup, run the command:
rails new deep_field_photos --skip-javascript --skip-importmap
Installing Vite
Next, add gem 'vite_rails'
to your gemfile and run bundle install
. Then, run bundle exec vite install
, which sets up the Vite configuration in Rails. The most important file that this
creates is vite.config.ts
, which is where you can add plugins such as Vue. Currently it should contain the code:
import { defineConfig } from 'vite'
import RubyPlugin from 'vite-plugin-ruby'
export default defineConfig({
plugins: [
RubyPlugin(),
],
})
Installing Vue, Vue Router, and Vuetify
Now we want to install Vue, Vue Router, and Vuetify, and add Vue to the vite.config.ts
file. To install them we will use Yarn, which is a package manager for JavaScript. If you don’t already have Yarn installed, you can follow the installation instructions here. Now, run yarn add vue @vitejs/plugin-vue vuetify @mdi/font vue-router
. Then, update your vite.config.ts
file to have the following code:
import { defineConfig } from 'vite'
import RubyPlugin from 'vite-plugin-ruby'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [
vue(),
RubyPlugin(),
],
})
Creating The Home Controller and /
Route
Now that we have the setup in place for Vite and Vue, let’s create a route to test it. First,
create a controller file, home_controller.rb
, and paste the following code into it.
class HomeController < ApplicationController
def index
end
end
Next, create a new folder called home
in the views
folder, and create a file in it called index.html.erb
with the line:
<div id="app"></div>
Finally, in your routes.rb
file, add the following lines:
get '/', to: 'home#index'
get '*path', to: 'home#index', constraints: lambda { |request|
!request.xhr? && request.format.html?
}
This will create a single page application that allows us to mount Vue and use the Vue Router on the frontend. The constraints on the second catch-all route will route all html requests to home#index
while ignoring JSON requests. This setup enables you to add additional routes below it for handling API requests, allowing Rails to serve as the backend API for a Vue frontend.
Setting Up Foreman
With that setup in place, we just need a way to start the Rails server and Vite server at the same time. Foreman is a process manager commonly used in Rails apps that will do this for us. If you don’t already have it installed, you can run gem install foreman
. Next, create a Procfile
at the top level of your deep_field_photos
directory with the following lines:
web: bundle exec rails server -p 3000
vite: bin/vite dev
Run foreman start
and you should see both servers start up. Visit http://localhost:3000
in your
browser and you should see a blank page.
To verify that Vite is running, open the JavaScript console in your browser devtools and you should see ‘Vite ⚡️ Rails’ and ‘Visit the guide for more information: https://vite-ruby.netlify.app/guide/rails' logged to the console. These come from the console.log
statements in app/frontend/entrypoints/application.js
which was auto-generated during the Vite install. With this in place, we can continue with creating our Vue app.
Setting Up The Vue Router
With the backend setup in place and the Vue Router already installed, let’s create a basic application that displays some images from the Hubble and James Webb telescopes. Create a file in the frontend
folder called router.js
and put the following code in it.
import { createRouter, createWebHistory } from 'vue-router'
import Home from './components/Home.vue'
import Hubble from './components/Hubble.vue'
import JamesWebb from './components/JamesWebb.vue'
const routes = [
{ path: '/', component: Home },
{ path: '/hubble', component: Hubble },
{ path: '/james_webb', component: JamesWebb },
]
const router = createRouter({
history: createWebHistory(),
routes,
})
export default router
Finally, replace the existing code in app/frontend/entrypoints/application.js
with the following code which sets up Vuetify and mounts the Vue app on the view rendered by the HomeController
in Rails.
import { createApp } from 'vue'
import App from '../components/App.vue'
import router from '../router.js'
import { createVuetify } from 'vuetify';
import 'vuetify/styles';
import * as components from 'vuetify/components';
import * as directives from 'vuetify/directives';
import '@mdi/font/css/materialdesignicons.css';
const vuetify = createVuetify({
components,
directives,
});
document.addEventListener('DOMContentLoaded', () => {
const app = createApp(App);
app.use(router);
app.use(vuetify);
app.mount('#app');
});
Creating The Components
The final step is to create the components that were referenced in application.js
and router.js
: App
, Home
, Hubble
, and JamesWebb
. Create a folder inside the frontend
directory called components
and add an App.vue
file to it with the following code.
<template>
<v-app>
<v-main>
<router-view></router-view>
</v-main>
</v-app>
</template>
<script>
export default {
name: 'App',
}
</script>
This will allow the Vue router to take over rather than using the Rails router on the backend.
Next, create a Home.vue
file with the following code to create a header and two buttons linking
to the two routes created in router.js
.
<template>
<v-container class="d-flex flex-column justify-center text-center">
<h1>Deep Field Photos</h1>
<v-row>
<v-col cols="12" class="d-flex justify-center">
<v-btn to="/hubble" class="ma-2" color="primary">Hubble</v-btn>
<v-btn to="/james_webb" class="ma-2" color="primary">James Webb</v-btn>
</v-col>
</v-row>
</v-container>
</template>
<script>
export default {
name: 'DeepFieldPhotos',
}
</script>
Before creating the Hubble
and JamesWebb
components, we need to add the images which will be
displayed in them. Inside the frontend
directory, create an assets folder, and within it,
create an images folder (frontend/assets/images
). Then, download and save the two deep field
images from the NASA website inside the new images
folder. The images can be accessed here and here, and you can call them ‘hubble_deep_field’ and ‘james_webb_deep_field’. With the images in place, we can proceed with creating the Hubble.vue
file with the following code. This code assumes the images have the .webp
extension, but you can adjust this to match the actual extension of the images you saved.
<template>
<img src="../assets/images/hubble_deep_field.webp" width="400" height="300"></img>
<p>The Hubble Deep Field Photo, taken in 1995, was a groundbreaking image that captured a tiny,
seemingly empty patch of sky over the course of 10 consecutive days. What emerged from this deep
exposure was a stunning array of over 3,000 galaxies, some of the most distant and ancient ever seen
at the time. The photo demonstrated Hubble's remarkable capacity to look far into space and time,
fundamentally changing our understanding of the universe.</p>
</template>
<script>
export default {
name: 'Hubble',
}
</script>
Finally, create the JamesWebb.vue
file with the following code.
<template>
<img src="../assets/images/james_webb_deep_field.webp" width="400" height="300"></img>
<p>The James Webb Deep Field Photo represents the most detailed image of the early universe ever captured.
Taken using the James Webb Space Telescope’s powerful infrared capabilities, it allows us to see galaxies
that existed over 13 billion years ago, just a few hundred million years after the Big Bang. Launched in
2021, Webb's ability to peer through cosmic dust and capture faint light gives scientists an unprecedented
view into the formation of the first galaxies.</p>
</template>
<script>
export default {
name: 'JamesWebb',
}
</script>
Stop and restart Foreman (Ctrl-C and then foreman start
) to make sure all the changes register,
and you should be able to navigate to the /hubble
and /james_webb
routes either using the buttons on the home page or by typing the route into your browser. You can find the source code for the example application here.
Conclusion
This is a simple and contrived example application, but it sets you up to be able to create more complex applications in the future with Rails, Vue, and Vite. With this pattern in place, you can store data on the backend and use Rails as an API to manipulate and serve the data to your Vue app. By following this approach, you’ll be well-equipped to build dynamic, full-stack applications that combine the robust features of Rails with the flexibility and modern UI capabilities of Vue and Vuetify.