Building a Vue SPA with Laravel
Published on by Paul Redmond
Building a Vue single page application (SPA) with Laravel is a beautiful combination for building clean API-driven applications. In this tutorial, we show you how to get up and running with Vue router and a Laravel backend for building a SPA. We will focus on the wiring up all the pieces needed, and then in a follow-up tutorial, we will further demonstrate using Laravel as the API layer.
The primary flow of how an Vue SPA works with Laravel as a backend is as follows:
- The first request hits the server-side Laravel router
- Laravel renders the SPA layout
- Subsequent requests leverage the
history.pushState
API for URL navigation without a page reload
Vue router can be configured to use history mode or the default hash-mode, which uses the URL hash to simulate a full URL so the page won’t reload when the URL changes.
We will use history mode, which means we need to configure a Laravel route that will match all possible URLs depending on which route the user enters the Vue SPA. For example, if the user refreshes a /hello
route, we’ll need to match that route and return the Vue SPA application template. The Vue Router will then determine the route and render the appropriate component.
Installation
To get started, we will create a new Laravel project and then install the Vue router NPM package:
laravel new vue-routercd vue-router # Link the project if you use Valetvalet link # Install NPM dependencies and add vue-routeryarn installyarn add vue-router # or npm install vue-router
We have a Laravel installation and the vue-router
NPM package ready to go. Next, we’ll configure the router and
define a couple of routes and components.
Configuring Vue Router
The way that Vue Router works is that it maps a route to a Vue component and then renders it within this tag in the application:
<router-view></router-view>
The router view is within the context of the Vue application component that surrounds the entire application. We will come back to the App
component momentarily.
First, we are going to update the main JavaScript file resources/assets/js/app.js
and configure Vue router. Replace the contents of the app.js
file with the following:
import Vue from 'vue'import VueRouter from 'vue-router' Vue.use(VueRouter) import App from './views/App'import Hello from './views/Hello'import Home from './views/Home' const router = new VueRouter({ mode: 'history', routes: [ { path: '/', name: 'home', component: Home }, { path: '/hello', name: 'hello', component: Hello, }, ],}); const app = new Vue({ el: '#app', components: { App }, router,});
We have a few files that we need to create, but first, let’s cover the contents of app.js
:
- We import and install the
VueRouter
plugin withVue.use()
- We import three Vue components:
- an
App
component that is the outermost application component, - A
Hello
component that maps to the/hello
route - A
Home
component that maps to the/
route
- an
- We construct a new
VueRouter
instance that takes a configuration object - We make Vue aware of the
App
component by passing it to thecomponents
property in the Vue constructor - We inject the
router
constant into the new Vue application to get access tothis.$router
andthis.$route
The VueRouter
constructor takes an array of routes, where we define the path, the name (just like Laravel’s named route), and the component that maps to the path.
I like to move my route definitions into a separate routes module that I import, but for the sake of simplicity we’ll define the routes within the main application file.
In order for Laravel mix to run successfully, we need to define the three components:
mkdir resources/assets/js/viewstouch resources/assets/js/views/App.vuetouch resources/assets/js/views/Home.vuetouch resources/assets/js/views/Hello.vue
First, the App.vue
file is the outermost container element for our application. Inside this component, we’ll define an application heading and some navigation using Vue Router’s <router-link/>
tag:
<template> <div> <h1>Vue Router Demo App</h1> <p> <router-link :to="{ name: 'home' }">Home</router-link> | <router-link :to="{ name: 'hello' }">Hello World</router-link> </p> <div class="container"> <router-view></router-view> </div> </div></template><script> export default {}</script>
The most important tag in our App
component is the <router-view></router-view>
tag, which is where our router will render the given component that matches the route (i.e. Home
or Hello
).
The next component we need to define is located at resources/assets/js/views/Home.vue
:
<template> <p>This is the homepage</p></template>
Lastly, we define the Hello
component located at resources/assets/js/views/Hello.vue
:
<template> <p>Hello World!</p></template>
I like separating my reusable components from my view-specific components by organizing my views into the resources/assets/js/views
folder and my truly reusable components in resources/assets/js/components
. This is my convention, and I find it works well so I can easily separate which components are intended to be reusable and which components are view-specific.
We have everything we need to run our Vue application as far as the frontend is concerned! Next, we need to define the backend route and the server-side template.
The Server-Side
We leverage an application framework like Laravel with a Vue SPA so that we can build a server-side API to work with our application. We can also use Blade to render our application and expose environment configuration through a global JavaScript object, which is convenient in my opinion.
In this tutorial, we aren’t going to build out an API, but we will in a follow-up. This post is all about wiring up the Vue router.
The first thing we’ll tackle on the server-side is defining the route. Open the routes/web.php
file and replace the welcome route with the following:
<?php /*|--------------------------------------------------------------------------| Web Routes|--------------------------------------------------------------------------|| Here is where you can register web routes for your application. These| routes are loaded by the RouteServiceProvider within a group which| contains the "web" middleware group. Now create something great!|*/ Route::get('/{any}', 'SpaController@index')->where('any', '.*');
We define a catch-all route to the SpaController
which means that any web route will map to our SPA. If we didn’t do this, and the user made a request to /hello
, Laravel would respond with a 404.
Next, we need to create the SpaController
and define the view:
php artisan make:controller SpaController
Open the SpaController
and enter the following:
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; class SpaController extends Controller{ public function index() { return view('spa'); }}
Lastly, enter the following in resources/views/spa.blade.php
:
<html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Vue SPA Demo</title></head><body> <div id="app"> <app></app> </div> <script src="{{ mix('js/app.js') }}"></script></body></html>
We’ve defined the required #app
element which contains the App
component that Vue will render, along with rendering the appropriate component based on the URL.
Running the Application
The foundation is in place for building an SPA with Vue and Vue Router. We need to build or JavaScript to test it out:
yarn watch # or npm run watch
If you load up the application in your browser you should see something like the following:
Going Forward
We have the skeleton for a Vue SPA that we can start building using Laravel as the API layer. This app still has much to be desired that we will cover in a follow-up tutorial:
- Define a catch-all 404 route on the frontend
- Using route parameters
- Child routes
- Making an API request from a component to Laravel
- Probably much more that I’m not going to list here…
The goal of this tutorial was the lay the groundwork for showing you how easy you can start an SPA with Vue Router. If you’re not familiar with it, check out the Vue Router documentation
Now, onward to Part 2!