One of the nice features of vue-router is that it comes with a HTML5 history mode. The default mode is hash mode, which makes your URLs looks like this: https://example.com/#/login
. That's ok, but not as nice as using the hashless https://example.com/login
path. HTML5 mode allows you to do just, in a super simple way.
So, how do you enable HTML mode? Simple, just like this:
// client/src/router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import Index from '@/components/Index'
import Login from '@/components/Login'
Vue.use(Router)
export default new Router({
mode: 'history',
routes: [
{
path: '/',
name: 'Index',
component: Index
},
{
path: '/login',
name: 'Login',
component: Login
}
]
})
Adding the mode: 'history'
setting on the router enables the nice URL syntax.
The only big issue with HTML5 mode, is that if a user goes straight to a nested route (anything other than /
), or refreshes the page on a nested route, vue-router won't be able to intercept the request and serve up the proper page. Those requests will go directly to your Rails 5 API backend. In order to get around this, you can create a redirect from your Rails API backend to VueJS with information about which page the user is really looking for. Here is and example of how your Rails router might look:
# config/routes.rb
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
# v1 api routes here
end
end
match "/*path", to: redirect("/?redirect=%{path}"), via: :all
end
That last line inside the routes block will redirect to the /
route (which vue is mounted on) with information about the requested resource (/?redirect=%{path}
). For example, that last line would redirect a GET https://example.com/login
request to a GET https://example.com/?redirect=login
Now that we have given vue-router all the information it needs, we can add a path that will load the proper page:
// client/src/router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import Index from '@/components/Index'
import Login from '@/components/Login'
import NotFound from '@/components/NotFound'
Vue.use(Router)
export default new Router({
mode: 'history',
routes: [
{
path: '/',
name: 'Redirect',
// vue-router lets us define a redirect method, the target route `to` is available for the redirect function
// https://router.vuejs.org/en/essentials/redirect-and-alias.html
redirect: function (to) {
if (to.query.redirect) {
// This will clear the ?redirect=<path> from the end URL
var path = to.query.redirect
delete to.query.redirect
return {
path: '/' + path,
query: to.query
}
} else {
return {
path: '/index',
query: to.query
}
}
}
},
{
path: '/index',
name: 'Index',
component: Index
},
{
path: '/login',
name: 'Login',
component: Login
},
{
path: '*',
name: 'NotFound',
component: NotFound
}
]
})
There it is, a working redirect solution between your Rails API and VueJS front-end using HTML5 navigation mode. Now, there are a couple things to note here:
We had to move the "Index" component off the '/' path. In my example above I moved it to '/index'. This means that if someone goes to the homepage, the router will redirect to
https://example.com/index
. I don't love that, but I could not find a way to render a component from within the redirect function. If you know of a way, please tweet at me.We delete the
redirect
query parameters on redirect. For example, if you didn't delete that query parmeter, and visitedhttps://example.com/?redirect=login
, you would be redirected tohttps://example.com/login?redirect=login
. This cleans up pure redirects while still preserving any other query parameters.I have also added a
NotFound
component that will render a 404 page if someone tries to redirect to a page that doesn't exist. For example, if a user went tohttps://example.com/?redirect=not-really-a-page
. they would hit theRedirect
route, which would send them tohttps://example.com/not-really-a-page
, which would get matched to thepath: '*'
matcher and render the 404 page.