Server-Side Rendering With Laravel and Vue.js 2.5
Server-side rendering is a great way to increase the perception of loading speed in your full-stack app. Users get a complete page with visible content when they load your site, as opposed to an empty page that doesn't get populated until JavaScript runs.
One of the downsides of using Laravel as a backend for Vue.js was the inability to server render your code. Was. The release of Vue.js 2.5.0 has brought server-side rendering support to non-Node.js environments including PHP, Python, Ruby, etc.
In this tutorial, I'll take you through the setup steps for Laravel and demonstrate a simple server-rendered app. Get the code for this here on GitHub.
Quick Overview of Server-Side Rendering
If you aren't familiar with server-side rendering (SSR), here's a simple example: say we have a Vue.js app built with components. If we use the browser dev tools to view the page DOM after the page has loaded, we will see our fully rendered app:
<div id="app">
<ul>
<li>Component 1</li>
<li>Component 2</li>
<li>
<div>Component 3</div>
</li>
</ul>
</div>
But if we view the source of the document, i.e. index.html, as it was when sent by the server, you'll see it just has our mount element:
xxxxxxxxxx
<div id="app"></div>
Why the discrepancy? Because JavaScript is responsible for building the page, and, ipso facto, JavaScript has to run before the page is built. Fresh off the server, the page will have no content.
But with server-side rendering, our page includes the HTML needed for the browser to build a DOM before JavaScript is downloaded and run, i.e. the page source would look like the first example above. This is achieved by running the Vue.js app on the server and capturing the output, then injecting that output into the page before it is sent to the user.
With SSR, your app does not load or run any faster, indeed it may run slightly slower as the server has the added task of rendering the app. But the page content is shown sooner, therefore, the user can see engage with the page sooner.
You may also like: Client-Side v/s Server-Side Rendering: What to Choose When?
Why Couldn't Laravel Do Vue SSR Until Now?
Obviously, SSR requires a JavaScript environment on the server, as a Vue app is made with JavaScript. For non-Node.js backends like PHP, Ruby, and Python, a JavaScript sandbox must be spawned from the server to run the Vue app and generate an output.
V8Js is a project that allows you to install the V8 JavaScript runtime within a PHP environment and create such a sandbox. But until Vue version 2.5.0, this was still not adequate as Vue SSR required certain Node.js APIs to run correctly. The recent update has made sure the server renderer is now "environment agnostic" and can, therefore, be run in Node.js, V8Js, Nashorn, etc.
Vue/Laravel SSR Demo
Let's now get a simple demo of Vue SSR in a Laravel app.
Environment
php-v8js is the PHP extension that will give access to Google's V8 JavaScript engine. Undoubtedly the trickiest part of setting up Vue SSR with PHP is getting V8Js installed. Due to my limited Linux knowledge, it, in fact, took me several hours to get it working.
If you have a bit of skill with DevOps, you might try installing it yourself. If not, I recommend you use this Docker image and install Laravel on that.
Installing Dependencies
Once you have the extension working and have a fresh Laravel project, you'll need to install both Vue and vue-server-renderer. You'll need a minimum version of 2.5.0 to get the environment-agnostic SSR features.
xxxxxxxxxx
npm i --save-dev vue@>=2.5.0 vue-server-renderer@>=2.5.0
Vue.js
Let's begin by setting up a simple full-stack Vue.js/Laravel app. This won't have any SSR features yet, but we'll be laying the foundations that we'll need. To start, we'll put the app's main functionality into a single-file component, App.vue.
resources/assets/js/components/App.vue
xxxxxxxxxx
<template>
<div id="app">
{{ message }}
</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello World'
}
}
}
</script>
Our app entry file, app.js, will only be responsible for rendering the component and mounting it to the template. Using a render function here instead of a DOM template is essential for reasons that will soon be clear.
resources/assets/js/app.js
xxxxxxxxxx
import App from './components/App.vue';
import Vue from 'vue';
new Vue({
el: '#app'
render: h => h(App)
});
Mix Configuration
Let's set up a Mix configuration that builds the entry file. Note that I'm also overwriting the default Vue build to use the runtime-only build. Since we're using render functions and single-file components we won't need the template renderer.
webpack.mix.js
xxxxxxxxxx
let mix = require('laravel-mix');
mix
.js('resources/assets/js/app.js', 'public/js')
;
mix.webpackConfig({
resolve: {
alias: {
'vue$': 'vue/dist/vue.runtime.common.js'
}
}
});
With that done, you should be able to build the Vue.js app:
xxxxxxxxxx
$ npm run dev
## Outputs to public/js/app.js
Blade View
We'll need a Blade template to deliver our Vue app to the browser. Make sure to include an empty div
with id app
which will serve as the mount element. Also, include the build script.
resources/views/app.blade.php