Server-side rendering with Vue and Nuxt.js
Brian Neville-O'Neill
Posted on July 8, 2019
Server-side Rendering (SSR) or Universal Rendering, as it is sometimes called, is not a new concept in web development. Before the increasing popularity of single-page applications, a web-page typically received an HTML(in most cases accompanied with some images, style sheet, and JavaScript) response after making a request to the server. Which is then rendered on the browser.
This worked quite well for a while since most web-pages then were mainly just for displaying static images and text, and had little interactivity. Today, however, this is no longer the case as many websites have morphed into full-fledged applications often requiring interactive user interfaces. With this requirement comes the need to manipulate the DOM using JavaScript which could be tedious and fraught with many inefficiencies, often leading to poor performance and slow user interfaces.
SSR began to take a back seat as new JavaScript frameworks such as React, Angular and Vue were introduced which made it quicker and more efficient to build user interfaces. These frameworks introduced the concept of the virtual DOM where a representation of the user interface is kept in memory and synced with the real DOM. Also, instead of getting all of the content from the HTML document itself, you are getting a bare-bones HTML document with a JavaScript file that will make requests to the server, get a response (most likely JSON) and generate the appropriate HTML. This is called Client-side rendering (CSR).
In SSR, the source HTML file will typically look like this:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello World</title>
</head>
<body>
<h1>My Website</h1>
<p>Welcome to my new website</p>
<p>This is some more content</p>
</body>
</html>
Whereas in CSR and when using a JavaScript framework such as Vue, the source file will look like this:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello World</title>
</head>
<body>
<div id="root">
<app></app>
</div>
<script src="https://vuejs.org"type="text/javascript"></script>
</body>
</html>
As you can see instead of having content inside HTML tags, you have a container div with an id of root. In this container, we have a special tag, app which will contain content parsed by Vue. The server is now only responsible for loading the bare minimum of the website or application. Everything else is handled by a client-side JavaScript library, in this case, Vue.
Pros and cons
The advantages and disadvantages of each method can be summarized as follows:
Why Nuxt.js
As outlined in the previous section, one of the problems with CSR or a typical single page application is SEO as many search engines cannot crawl your application as intended. Though in recent years there has been an update in Google’s algorithm to better handle these situations, it’s not quite perfect yet.
How do we bring in the advantages of SSR in a single page application? Nuxt.js is a framework that builds on the SSR features Vue already offers, making it easier to build SSR applications. The advantages Nuxt brings include:
- SSR, which helps with SEO, quicker initial page loads
- Automatic code splitting
- Static file serving
- Intelligent defaults and pre-configuration for Vuex, Vue Router and vue-meta
- Provides a standard folder structure for your application
- Automatic routing configuration
- Modular system makes it easy to customize the framework
Getting started with Nuxt
To see Nuxt in action, first, make sure you have a dependency manager such as Yarn installed. On Windows, this can be easily installed by downloading and running the executable file from the Yarn installation page. Alternatively, you could use NPM.
Let’s scaffold a new project called nuxt-ssr by running the following command: yarn create nuxt-app nuxt-ssr
After a few installations, you will see a series of prompts. As this is just an introductory article on Nuxt, we would select the most minimal options to keep things simple:
- For Project name and Project description you can select the default values
- For Use a custom server framework select none
- For Choose features to install select Axios
- For Use a custom UI framework and Use a custom test framework select none
- For Choose rendering mode select Universal (this is the option for SSR)
- For Author name enter your name
- For Choose a package manager select yarn
Next go into the nuxt-ssr directory, then run the command yarn run dev:
Visit http:\localhost:3000 on your browser, you should see something like this:
This content is Server-side rendered. If you right-click on the page and view page source, you will be able to see the HTML elements. For example, you should be able to find this in the body tag:
<h2 class="subtitle">
My excellent Nuxt.js project
</h2>
This means that a search engine bot will be able to find and index this content.
Remember one of the questions in the prompt earlier asked us to Choose rendering mode. Now let’s see the difference if we were to have chosen the option single-page application. In the nuxt-ssrdirectory edit the file nuxt-config.js changing the mode property from universal to spa:
import pkg from './package'
export default {
mode: 'spa',
/*
** Headers of the page
*/
head: {
title: pkg.name,
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: pkg.description }
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
]
},
/*
** Customize the progress-bar color
*/
loading: { color: '#fff' },
/*
** Global CSS
*/
css: [
],
/*
** Plugins to load before mounting the App
*/
plugins: [
],
/*
** Nuxt.js modules
*/
modules: [
// Doc: https://axios.nuxtjs.org/usage
'@nuxtjs/axios',
],
/*
** Axios module configuration
*/
axios: {
// See https://github.com/nuxt-community/axios-module#options
},
/*
** Build configuration
*/
build: {
/*
** You can extend webpack config here
*/
extend(config, ctx) {
}
}
}
Then stop the application by pressing Ctrl-c on your terminal. Run the application again with yarn run dev. When you visit the page again and view page source, you will not be able to find the content we had previously in the body section.
Let’s revert back to universal mode, and restart the application.
Directory structure
Let’s take a look at the directory structure of a typical Nuxt application. Open the nuxt-ssr directory and you should see a structure like this:
The directories which contain .vue files are components, layouts and pages. The components directory contains our reusable Vue components, the layouts directory as its name implies contains layout components. In this directory, you will find a default.vue file, this file is a component but wraps the nuxt components. Everything in this file is shared among all other pages while each page content replaces the nuxt component.
The pages directory contains the top level views and routes are automatically generated for any .vue file in this directory.
In the .store directory we store our Vuex files for state management, the static directory contains files that we want to serve exactly as they are for example robots.txt or favicon. The assets directory contains our un-compiled assets-things that need to be compiled when you deploy to production for example stylus, SASS, images, and fonts. In the plugins directory, we put external JavaScript plugins to load before starting the Vue application.
In the middleware directory we put in custom functions to run before rendering a layout or page. Lastly, we have the nuxt.config.js file which we edited earlier, this file is used to modify the default Nuxt configuration.
Navigation component
Let’s create a simple navigation component that will be visible on all our pages. In the layouts directory create a folder called partials. In this folder, create a file called nav.vue and enter the following code:
<template>
<header>
<nuxt-link to="/" class="logo">Nuxt-SSR</nuxt-link>
<nav>
<ul>
<li><nuxt-link to="/">Home</nuxt-link></li>
<li><nuxt-link to="about">About</nuxt-link></li>
<li><nuxt-link to="services">Services</nuxt-link></li>
<li><nuxt-link to="contact">Contact</nuxt-link></li>
</ul>
</nav>
</header>
</template>
<script>
export default {
}
</script>
<style>
header {
background: rgb(0, 000, 000);
display: grid;
grid-template-columns: repeat(2,auto);
}
.logo, li a {
padding: 1em 2em;
display: block;
text-transform: uppercase;
text-decoration: none;
font-weight: bold;
color: white;
font-size: .9em;
}
nav {
justify-self: right;
}
ul {
list-style-type: none;
}
li {
display: inline;
}
li a {
padding: 1em 2em;
display: inline-block;
background: rgba(0,0,0,0.1);
}
</style>
The component shows four links to pages we will create in a moment. Notice that for Nuxt to handle routing appropriately, we are not using the tag but the component. We also have some CSS code which we won’t discuss here.
Next, open the defaults.vue file in the layouts directory and replace its content with the following:
<template>
<div>
<Nav />
<nuxt />
</div>
</template>
<script>
import Nav from './partials/nav';
export default {
components: {
Nav
}
}
</script>
<style>
html {
font-family: 'Source Sans Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI',
Roboto, 'Helvetica Neue', Arial, sans-serif;
font-size: 16px;
word-spacing: 1px;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
box-sizing: border-box;
}
*,
*:before,
*:after {
box-sizing: border-box;
margin: 0;
}
.button--green {
display: inline-block;
border-radius: 4px;
border: 1px solid #3b8070;
color: #3b8070;
text-decoration: none;
padding: 10px 30px;
}
.button--green:hover {
color: #fff;
background-color: #3b8070;
}
.button--grey {
display: inline-block;
border-radius: 4px;
border: 1px solid #35495e;
color: #35495e;
text-decoration: none;
padding: 10px 30px;
margin-left: 15px;
}
.button--grey:hover {
color: #fff;
background-color: #35495e;
}
</style>
Here, we have imported our nav component in the script section and then displaying it in the template section. To make our page look better, let’s include a font. There are multiple ways to include a font in Nuxt. One way is by adding it to the nuxt-config.js file. Under the head object of this file, change the link array so it looks like this:
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
{ rel: 'stylesheet', href: 'https://fonts.googleapis.com/css?family=Montserrat:300,700' },
]
Then edit the css array, close to the bottom of the file as follows:
css: [
'@/assets/css/main.css'
],
This is where we define a global css file that will apply to all of our application. Then we will have to create a css folder and the main.css file in the assets directory. Enter the following in this file:
body {
font-family: 'Montserrat';
margin: 0;
}
section {
padding: 5em;
}
h1 {
font-size: 3em;
text-transform: uppercase;
}
.subheading {
font-size: 1.5em;
margin-bottom: 2em;
text-transform: uppercase;
color: rgb(179, 179, 179);
font-weight: bold;
}
p {
font-size: 1.2em;
line-height: 1.4em;
}
.page-enter-active {
animation: bounce-in .8s;
}
.page-leave-active {
animation: bounce-out .5s;
}
@keyframes bounce-in {
0% { transform: scale(.9); opacity: 0; }
100% { transform: scale(1); opacity: 1; }
}
@keyframes bounce-out {
0% { transform: scale(1); opacity: 1; }
100% { transform: scale(.9); opacity: 0; }
}
Pages and routing
Replace the contents of the pages/index.vue file with the following:
<template>
<section class="container">
<h1>The Best</h1>
<p class="subheading">Hello world</p>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolorem modi voluptate sequi excepturi natus, odio unde, neque voluptas, suscipit tempore dicta cupiditate sed nemo facilis ullam tenetur quidem? Dolore, expedita.</p>
</section>
</template>
<script>
export default {
}
</script>
<style>
</style>
Restart the application and you should see this:
As mentioned previously, routes in Nuxt are automatically generated from the pages directory. The page index.vue automatically defaults to the home route. For the route about, we simply create an about.vue file in the pages directory. You can do the same for services and contact, and you will see the routes just work!
Conclusion
This has been a brief overview of Nuxt.js and how it can be used to implement SSR. Nuxt.js comes with many more features which you can find in the official guide.
If you have any questions or comments, don’t hesitate to post them below.
Plug: LogRocket, a DVR for web apps
LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.
The post Server-side rendering with Vue and Nuxt.js appeared first on LogRocket Blog.
Posted on July 8, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.