Faking Server-Side Rendering With Vue.js and Laravel

DZone 's Guide to

Faking Server-Side Rendering With Vue.js and Laravel

Read on to learn how to perform SSR in your web applications without having to use a Node.js server on the back-end of your app.

· Web Dev Zone ·
Free Resource

Server-side rendering (SSR) is a design concept for full-stack web apps that provides a rendered page to the browser. The idea is that the page can be shown while the user waits for scripts to be downloaded and run.

If you aren't using a Node.js server for your app then you're out of luck; only a JavaScript server can render a JavaScript app.

However, there are alternatives to SSR that may be good enough, or even better, for some use cases. In this article, I'm going to explain a method I use to "fake" server-side rendering using Vue.js and Laravel.


Pre-rendering (PR) tries to achieve the same outcome as SSR by using a headless browser to render the app and capture the output to an HTML file, which is then served to the browser. The difference between this and SSR is that it is done ahead of time, not on-the-fly.

Limitation: User-Specific Content

Some pages, like the front page of your site, will probably contain general content, i.e. content that all users will view the same. But other pages, like admin pages, will contain user-specific content, for example, a user's name and birth date.

The limitation of PR is that it can't be used for pages that contain such content. As I just said, the pre-rendered templates are only made once and can't be customized. SSR does not have this limitation.

Faking Server-Side Rendering

My fake SSR method for Vue and Laravel is to pre-render a page, but replace any user-specific content with Laravel Blade tokens. When the page is served, Laravel's view helper will replace the tokens with user-specific content.

So before pre-rendering, your page will have this:

<div id="app"></div>

After pre-rendering, you'll have this:

<div id="app">
        Hello {{ $name }}, your birthday is {{ $birthday }}

And when the page is served by Laravel your browser receives the following, which is exactly what it would receive from SSR:

        Hello Anthony, your birthday is 25th October.

With this method, we get all the benefits of SSR but it can be done with a non-Node backend like Laravel.

How It's Done

I've setup this repo with a demo for you to refer to, but below I'll cover the main steps in getting this to work.

1. Vue.js App

Any user-specific content will need to be in a data property. We'll use a Vuex store to make this easier:

const store = new Vuex.Store({
  state: {
    // These are the user-specific content properties
    name: null,
    birthday: null

new Vue({
  el: '#app',

When the app is being pre-rendered, we want to set the user-specific data as strings containing Laravel Blade tokens. To do this, we'll use the Vuex replaceState method after the store is created, but before the app is mounted (we'll set the value of the global window.__SERVER__ shortly).

if (window.__SERVER__) {
    name: '{{ $name }}',
    birthday: '{{ $birthday }}'

Client-Side Hydration

When the Vue app mounts, we want it to take over the page. It's going to need the actual initial store state to do this, so let's provide it now rather than using AJAX. To do this, we'll put the initial state in a JSON-encoded string, which we'll create in the next step. For now, let's just create the logic by modifying the above code to the following:

if (window.__SERVER__) {
    name: '{{ $name }}',
    birthday: '{{ $birthday }}'
} else {

2. Blade Template

Let's set up a Blade template including:

  • A mount element for our Vue app.
  • Inline scripts to set the global variables discussed in the previous step.
  • Our Webpack build script.
<div id="app"></div>
<script>window.__INITIAL_STATE__='{!! json_encode($initial_state) !!}'</script>

The value of $initial_state will be set by Laravel when the page is served.

3. Webpack Configuration

We'll use the Webpack prerender-spa-plugin to do the pre-rendering. I've done a more detailed write up about how this works, but here's the concept in brief:

  1. Put a copy of the template in the Webpack build output using html-webpack-plugin.
  2. The prerender-spa-plugin will bootstrap PhantomJS, run our app, and overwrite the template copy with pre-rendered markup.
  3. Laravel will use this pre-rendered template as a view.
if (isProduction) {

    new HtmlWebpackPlugin({
      template: Mix.Paths.root('resources/views/index.blade.php'),
      inject: false

  var PrerenderSpaPlugin = require('prerender-spa-plugin');

    new PrerenderSpaPlugin(
      [ '/' ]

4. Post-Build Script

If you were to run Webpack now you'd have index.blade.php in your Webpack build folder and it would contain:

<div id="app">

There are some additional tasks we need to do before this can be used:

  1. Add the attribute server-rendered="true" to the mount element. This lets Vue know that we've already rendered the page and it will attempt a seamless takeover. The replace NPM module can do this job.
  2. Change window.__SERVER__=true to window.__SERVER__=false so that when the app runs in the browser it loads the store with the initial state.
  3. Move this file somewhere that your route can use it. Let's create a directory resources/views/rendered for this (it might also be a good idea to add this to .gitignore just as you would for your Webpack build).

We'll create a bash script render.sh to do all this:

#!/usr/bin/env bash
npm run production &&
mkdir -p resources/views/rendered
./node_modules/.bin/replace "<div id=\"app\">" "<div id=\"app\" server-rendered=\"true\">" public/index.html
./node_modules/.bin/replace "<script>window.__SERVER__=true</script>" "<script>window.__SERVER__=false</script>" public/index.html &&
mv public/index.html resources/views/rendered/index.blade.php

Now we can render or re-render our template anytime like this:

$ source ./render.sh

5. Route

The last step is to get our route in web.php to serve the pre-rendered template and use the view helper to replace the tokens with the user-specific data:

    $initial_state = [
        'name' => 'Anthony',
        'birthday' => '25th October'
    $initial_state['initial_state'] = $initial_state;
    return view('rendered.index', $initial_state);

The array $initial_state contains the user-specific data, though in a real app you'd probably first check that the user is authorized and grab the data from a database.

Performance Advantage of the Fake SSR Approach

The normal approach to displaying a page with user-specific content in a frontend app, for example, the one explained in Build an App with Vue.js: From Authentication to Calling an API. This requires some back-and-forth between the browser and server before it can actually display anything:

  1. Browser requests the page.
  2. An empty page is served and nothing is yet displayed.
  3. Browser requests a script.
  4. Script now runs, does an AJAX request to the server to get user-specific content.
  5. Content is returned so now the page finally has what it needs to display something.

With this approach, not only can we display something much earlier, we can also eliminate an unnecessary HTTP request:

  1. Browser requests a page.
  2. Complete page is supplied so the browser can display it straight away.
  3. Browser requests a script.
  4. Script now runs, has all the necessary content to seamlessly take over a page.

This, of course, is the advantage that real SSR has as well, the difference being that this approach makes it achievable with a non-Node.js server like Laravel!


  • This is a fairly fragile and complicated setup. To be fair, setting up SSR is no walk in the park either.
  • Your webpack build will take longer.
  • If your data undergoes manipulation by JavaScript before it's displayed you have to recreate that manipulation on the server-side as well, in a different language. That will suck.
javascript ,vuejs ,laravel ,server-side ,web dev

Published at DZone with permission of Anthony Gore , DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}