Frontend Weekly
Published in

Frontend Weekly

Creating a Nuxt.js app fast and easy

Photo by Sigmund on Unsplash

Learn how to create a Nuxt.js application. This is a complete, step-by-step tutorial.

Over the recent years, many different frameworks for building static websites by using pre-rendering have emerged. Today I’m going to talk about one of them — Nuxt.js. This open source framework is built on top of another well known frontend framework, Vue.js, but I think it has a number of distinct advantages.

To back up our point of view I decided to build a real application from scratch using Nuxt.js. Repeat this experiment yourself with this step-by-step guide. Good luck!

Briefly about Nuxt.js

Nuxt is a high-level framework based on Vue. With Nuxt.js you can develop out-of-the-box isomorphic web applications by abstracting away the details of the server and client code distribution. With this approach, I save time and can focus on the development.

The key advantages of Nuxt:

  • SPA, SSR and pre-rendering are already configured; the only thing I need to do is to choose.

In this application I use a pre-rendering for the product-mode. It means that I generate all the pages of a web site in advance and then deploy to hosting for transmitting static data.

  • Great SEO for all search engines as the result of using SSR or pre-renderer.
  • Quick interaction with the site compared to static websites. It is achieved by loading only the necessary js chunks, css styles and API requests (most of the process is automated by webpack 4 working under the hood of Nuxt).
  • Excellent Google Lighthouse / Page Speed performance. With the right configuration, you can get 100/100 even on a weak server.
  • CSS Modules, Babel, Postscc and other nice tools are pre-configured using create-nuxt-app.
  • Default project structure allows for convenient work in medium-sized and large teams.
  • Over 50 ready-to-use modules and the ability to use any packages from the extensive Vue.js ecosystem.

I could go on and on about Nuxt advantages. This is the framework that I really love for its ease of use and the ability to create flexible and easily scalable applications. So, let’s start and see all the advantages in practice.

Find more information about Nuxt.js on the official website. Detailed guides are also available here.

Design

Well-arranged, ready-made design or, even better, a UI-kit, will make any application development much faster and easier. If you don’t have an available UI-designer at hand, that’s fine. Within the framework of our instruction, I will manage ourselves!

Specifically for this article, I prepared a modern, minimalist blog design with simple functionality, enough to demonstrate Nuxt performance capabilities.
For development, I used an online tool, Figma. The design and UI kit are available via this link. You can copy this template and use it in your project.

Creating a project

To create a project, I will use the create-nuxt-app utility from Nuxt developers, which allows configuring the application template with cli.

Initialize a project and state its name:

npx create-nuxt-app nuxt-blog

Further, the utility will offer to choose a set of preferred libraries and packages over a few steps, after that it will independently download, adjust and configure them for the project.
You can see a complete list of the selected options on Github.

For this project the configuration with Typescript will be used. When developing in Vue with Typescript, you can use two APIs: Options API or Class API. They have the same functionality, but different syntax. Personally, I prefer Options API syntax, that’s why it will be used in our project.

After creating the project, I can run our application using the command: npm run dev. It will now be available on localhost: 3000.

Nuxt uses a webpack-dev-server with installed and configured HMR as a local server, which makes development fast and convenient.

Since I are creating a demo version of an application, I will not write tests for it. But I highly recommend not to neglect application testing in commercial development.
If you are new to this topic, I advise you to look at
Jest — a very simple but powerful tool that supports working with Nuxt in combination with vue-test-utils.

The project structure

Nuxt creates a default directory and file structure suitable for a quick development start.

-- Assets
-- Static
-- Pages
-- Middleware
-- Components
-- Layouts
-- Plugins
-- Store
-- nuxt.config.js
-- ...other files

This structure is perfectly suitable for our project, so I will not change it.
You can read more about the purpose of different directories on the Nuxt website.

Building an application

Before writing the code, let’s do the following:

1) Delete the starter components and pages created by Nuxt.
2) Install pug and scss to make development more convenient
and faster. Run the command:

npm i --save-dev pug pug-plain-loader node-sass sass-loader fibers

After that, the lang attribute will become available for the template and style tags.

<template lang="pug"></template><style lang="scss"></style>

3) Add support for deep selector ::v-deep to the stylelint configuration, which will allow you to apply styles to child components, ignoring scoped. You can read more about this selector here.

{
rules: {
'at-rule-no-unknown': null,
'selector-pseudo-element-no-unknown': [
true,
{
ignorePseudoElements: ['v-deep'],
},
],
},
}

All preparations are over, let’s go to the next step.

Posts

Posts will be stored in the content/posts, directory, that I will create at the root of the project as a set of markdown files.

Let’s create 5 small files, so that I can start working with them right away. To make it simple, name them 1.md, 2.md, etc.

In the content directory, create a Posts.d.ts file, where I define the types for the object containing all the necessary information about the post:

export type Post = { 
id: number
title: string
desc: string
file: string
img: string
}

I think the meanings of the fields should be clear from their names.

Moving on. In the same directory, create another file called posts.ts with the following content:

import { Post } from './Post'  export default [  
{
id: 1,
title: 'Post 1',
desc:
'A short description of the post to keep the user interested.' +
' Description can be of different lengths, blocks are aligned' +
' to the height of the block with the longest description',
file: 'content/posts/1.md',
img: 'assets/images/1.svg',
},
... {
id: 5,
title: 'Post 5',
desc:
'A short description of the post to keep the user interested.' +
' Description can be of different lengths, blocks are aligned' +
' to the height of the block with the longest description',
file: 'content/posts/5.md',
img: 'assets/images/5.svg',
},
] as Post[]

In the img property, I refer to images in the assets/images directory, but I haven't created this directory yet, so let's do it now.

Now, let’s add images in .svg format to the created directory with the names that I specified above.

I will take 5 images from unDraw. This great resource is constantly updated and contains many free svg images.

Now that everything is ready, the content directory should look like this:

content/
-- posts.ts
-- Posts.d.ts
-- posts/
---- 1.md
---- 2.md
---- 3.md
---- 4.md
---- 5.md

And in the assets directory, the images subdirectory should have appeared with the following content:

assets/
-- images/
---- 1.svg
---- 2.svg
---- 3.svg
---- 4.svg
---- 5.svg
...

Dynamic file generation

Since I will get images and files with post texts dynamically, it’s necessary to implement a global mixin, which I can use further in all components.

To do this, create a mixins subdirectory in the plugins directory, and in the subdirectory create a getDynamicFile.ts file with the following content:

import Vue from 'vue'  export const methods = {  
getDynamicFile(name: string) {
return require(`@/${name}`)
},
}
Vue.mixin({
methods,
})

All I need to do now is enable this mixin in the nuxt.config.js file:

{
plugins: [
'~plugins/mixins/getDynamicFile.ts',
],
}

Fonts

After creating posts let’s enable fonts. The easiest way is to use the wonderful Webfontloader library, which allows you to get any font from Google Fonts. However, in commercial development proprietary fonts are used more often, so let’s look at such a case here.

As the font for our application I chose Rubik, which is distributed under the Open Font License. It can also be downloaded from Google Fonts.

Please note that in the downloaded archive the fonts will be in the otf format, but since I are working with the web, the woff and woff2 formats will be our best choice.They have smaller size than other formats, but they are fully supported in all modern browsers. To convert otf to the necessary formats, you can use one of the many free online services.

So, I have the fonts in the needed formats, now it’s time to add them to the project. To do this, create a fonts subdirectory in the static directory and add the fonts there. Create a fonts.css file in the same directory; it will be responsible for adding our fonts in the application with the following content:

@font-face {  
font-family: "Rubik-Regular";
font-weight: normal;
font-style: normal;
font-display: swap;
src:
local("Rubik"),
local("Rubik-Regular"),
local("Rubik Regular"),
url("/fonts/Rubik-Regular.woff2") format("woff2"),
url("/fonts/Rubik-Regular.woff") format("woff");
}
...

You can see the full contents of the file in the repository.

You should pay attention to two things:

1) I specify font-display: swap;, defining how the font added via font-face will be displayed depending on whether it has loaded and is ready to use.

In this case, I do not set a block period and set an infinite swap period. Which means that the font is loaded as a background process without blocking the page loading, and the font will be displayed when ready.

2) In src, I set the load order by priority.

First, I check if the needed font is installed on the user’s device by checking possible variations of the font name. If you don’t find it, check if the browser supports the more modern woff2 format, and if not, then use the woff format. There is a chance that the user has an outdated browser (for example, IE <9), in this case, I will further specify the fonts built into the browser as a fallback.

After creating the file with font loading rules, you need to add it in the application — in the nuxt.config.js file in the head section:

{
head: {
link: [
{
as: 'style',
rel: 'stylesheet preload prefetch',
href: '/fonts/fonts.css',
},
],
},
}

Note that here, as earlier, I are using the preload and prefetch properties, thereby setting high priority in the browser for loading these files without blocking the page rendering.
Let's add favicon to the static directory of our application right away, which can be generated using any free online service.

Now the static directory looks like this:

static/
-- fonts/
---- fonts.css
---- Rubik-Bold.woff2
---- Rubik-Bold.woff
---- Rubik-Medium.woff2
---- Rubik-Medium.woff
---- Rubik-Regular.woff2
---- Rubik-Regular.woff
-- favicon.ico

Moving on to the next step.

Reused styles

In our project, all the used styles are described with a single set of rules, which makes development much easier, so let’s transfer these styles from Figma to the project files.

In the assets directory, create a styles subdirectory, where I will store all styles reused in the project. And the styles directory will contain the variables.scss file with all our scss variables.

You can see the contents of the file in the repository.

Now I need to connect these variables to the project so that they are available in any of our components. In Nuxt the @nuxtjs/style-resources module is used for this purpose.

Let’s install this module:

npm i @nuxtjs/style-resources

And add the following lines to nuxt.config.js:

{
modules: [
'@nuxtjs/style-resources',
],
styleResources: {
scss: ['./assets/styles/variables.scss'],
},
}

Well done! Variables from this file will be available in any component.

The next step is to create a few helper classes and global styles that will be used throughout the application. This approach will allow you to centrally manage common styles and quickly adapt the application if the layout design is changed.

Create a global subdirectory in the assets/styles directory with the following files:

1) typography.scss file will contain all the helper classes to control text, including links.
Note that these helper classes change styles depending on the user's device resolution: smartphone or PC.

2) transitions.scss file will contain global animation styles, both for transitions between pages, and for animations inside components, if needed in the future.

3) other.scss file will contain global styles, which can not yet be separated into a specific group.

The page class will be used as a common container for all components on the page and will form the correct padding on the page.

The .section class will be used to denote the logical unit boundaries, and the .content class will be used to restrict the content width and its centering on the page. I will see how these classes are used further when I start implementing components and pages.

4) index.scss is a common file that will be used as a single export point for all global styles.

You can see the full contents of the file on Github.

At this step, I connect these global styles to make them available throughout the application. For this task Nuxt has a css section in the nuxt.config.js file:

{
css: ['~assets/styles/global'],
}

I should say that in the future, css classes will be assigned according to the following logic:

1) If a tag has both helper classes and local classes, then the local classes will be added directly to the tag, for example, p.some-local-class, and the helper classes will be specified in the class property, for example, class = "body3 medium ".

2) If a tag has either helper classes or local classes, they will be added directly to the tag.

I use this approach for my convenience, it helps to visually distinguish between global and local classes right away.

Before the development, let’s install and enable reset.css so that our layout looks the same in all browsers. To do this, I install the required package:

npm i reset-css

And enable it in the nuxt.config.js file in the already familiar css section, that will now look like this:

{
css: [
'~assets/styles/global',
'reset-css/reset.css',
],
}

Got it? If you did, I are ready to move on to the next step.

Layouts

In Nuxt, Layouts are wrapper files for our app that allow you to reuse common components between them and implement the necessary common logic. Since our application is pretty simple, it will be enough for us to use the default layout - default.vue.

Also, in Nuxt a separate layout is used for an error page like 404, which is actually a simple page.

Layouts in the repository.

default.vue

Our default.vue will have no logic and will look like this:

<template lang="pug">  
div
nuxt
db-footer
</template>

Here I use 2 components:
1) nuxt during the building process it will be replaced with a specific page requested by the user.
2) db-footer is our own Footer component (I will write it a little later), that will be automatically added to every page of our application.

error.vue

By default, when any error returns from the server in the http status, Nuxt redirects to layout/error.vue and passes an object containing the description of the received error via a prop named error.
Let's look at the script section, which will help to unify the work with the received errors:

<script lang="ts">  
import Vue from 'vue'
type Error = {
statusCode: number
message: string
}
type ErrorText = {
title: string
subtitle: string
}
type ErrorTexts = {
[key: number]: ErrorText
default: ErrorText
}
export default Vue.extend({
name: 'ErrorPage',
props: {
error: {
type: Object as () => Error,
required: true,
},
},
data: () => ({
texts: {
404: {
title: '404. Page not found',
subtitle: 'Something went wrong, no such address exists',
},
default: {
title: 'Unknown error',
subtitle: 'Something went wrong, but we`ll try to figure out what`s wrong',
},
} as ErrorTexts,
}),
computed: {
errorText(): ErrorText {
const { statusCode } = this.error
return this.texts[statusCode] || this.texts.default
},
},
})
</script>

What is going on here:

1) First, I define the types that will be used in this file.

2) In the data object, I create a dictionary that will contain all unique error messages for some specific, considerable errors I choose and a default message for all other errors.

3) In the computed errorText property I check if the received error is in the dictionary. If the error is there, then I return its message. If it’s not, I return the default message.

In this case our template will look like this:

<template lang="pug">  
section.section
.content
.ep__container
section-header(
:title="errorText.title"
:subtitle="errorText.subtitle"
)
nuxt-link.ep__link(
class="primary"
to="/"
) Home page
</template>

Note that here I are using the .section and .content global utility classes that I have created earlier in the assets/styles/global/other.scss file. They allow to center the content on the page.

Here the section-header component is used; it has not yet been created, but later it will be a universal component for displaying headers. I will implement it when I start discussing components.

The layouts directory look like this:

layouts/
-- default.vue
-- error.vue

Let’s move on to creating components.

Components

Components are the building blocks of our application. Let’s start with the components that I have already mentioned above.

I will not describe the components’ styles not to make this article too long. You can find them in the application repository.

SectionHeader

The headers in our application have the same style, so it makes total sense to use one component to display them and change the displayed data through the props.

Let’s look at the script section of this component.

<script lang="ts">  
import Vue from 'vue'
export default Vue.extend({
name: 'SectionHeader',
props: {
title: {
type: String,
required: true,
},
subtitle: {
type: String,
default: '',
},
},
})
</script>

Now let’s see what the template will look like:

<template lang="pug">  
section.section
.content
h1.sh__title(
class="h1"
) {{ title }}
p.sh__subtitle(
v-if="subtitle"
class="body2 regular"
) {{ subtitle }}
</template>

This component is a simple wrapper for the displayed data and does not have any logic.

LinkToHome

The simplest component in our application is the link above the title that leads to the home page from the selected post page.

This component is really tiny, so I will write all its code here (without styles):

<template lang="pug">  
section.section
.content
nuxt-link.lth__link(
to="/"
class="primary"
)
img.lth__link-icon(
src="~/assets/icons/home.svg"
alt="icon-home"
)
| Home
</template>
<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
name: 'LinkToHome',
})
</script>

Note that I request the home.svg icon from the assets/icons directory. You need to create this directory first and add there the necessary icon.

DbFooter

DbFooter component is very simple. It contains copyright and a link to generate a letter.

The requirements are clear, so let’s start the implementation from the script section.

<script lang="ts">  
import Vue from 'vue'
export default Vue.extend({
name: 'DbFooter',
computed: {
copyright(): string {
const year = new Date().getUTCFullYear()
return `© ${year} · All rights reserved`
},
},
})
</script>

In DbFooter there is only one computed property that returns the current year, concatenated with a given string Now let’s look at the template:

<template lang="pug">  
section.section
.content
.footer
a.secondary(
href="mailto:example@mail.com?subject=Nuxt blog"
) Contact us
p.footer__copyright(
class="body3 regular"
) {{ copyright }}
</template>

When clicking on the Contact us link, I will open the native mail client and immediately set the message subject. This solution is suitable for our application demo, but in real life, a more appropriate solution would be to implement a feedback form to send messages directly from the site.

PostCard

Postcard is a pretty simple component without any complexities.

<script lang="ts">  
import Vue from 'vue'
import { Post } from '~/content/Post'
export default Vue.extend({
name: 'PostCard',
props: {
post: {
type: Object as () => Post,
required: true,
},
},
computed: {
pageUrl(): string {
return `/post/${this.post.id}`
},
},
})
</script>

In the script section, I define one post prop, which will contain all the necessary information about the post.

I also implement the pageUrl computed property for use in the template, which will return us a link to the desired post page.

The template will look like this:

<template lang="pug">  
nuxt-link.pc(:to="pageUrl")
img.pc__img(
:src="getDynamicFile(post.img)"
:alt="`post-image-${post.id}`"
)
p.pc__title(class="body1 medium") {{ post.title }}
p.pc__subtitle(class="body3 regular") {{ post.desc }}
</template>

Note that the root element of the template is nuxt-link. This is done to enable the user to open the post in a new window using the mouse.

This is the first time that the getDynamicFile global mixin I created earlier in this article is used.

PostList

The main component on the home page consists of a post counter at the top and a list of posts.

The script section for this component:

<script lang="ts">  
import Vue from 'vue'
import posts from '~/content/posts'
export default Vue.extend({
name: 'PostList',
data: () => ({
posts,
}),
})
</script>

Note that after importing the array of posts, I add them to the data object so that the template has access to this data in the future.

The template looks like this:

<template lang="pug">  
section.section
.content
p.pl__count(class="body2 regular")
img.pl__count-icon(
src="~/assets/icons/list.svg"
alt="icon-list"
)
| Total {{ posts.length }} posts
.pl__items
post-card(
v-for="post in posts"
:key="post.id"
:post="post"
)
</template>

Don’t forget to add the list.svg icon to the assets/icons directory for everything to work as expected.

PostFull

PostFull is the main component on a separate post page that displays the post content.

For this component, I need the @nuxtjs/markdownit module, which is responsible for converting md to html.

Let’s install it:

npm i @nuxtjs/markdownit

Then let’s add @nuxtjs/markdownit to the modules section of the nuxt.config.js file:

{
modules: [
'@nuxtjs/markdownit',
],
}

Excellent! Let’s start implementing the component. As usual, from the script section:

<script lang="ts">  
import Vue from 'vue'
import { Post } from '~/content/Post'
export default Vue.extend({
name: 'PostFull',
props: {
post: {
type: Object as () => Post,
required: true,
},
},
})
</script>

In the script section, I define one prop post, which will contain all the necessary information about the post.

Let’s look at the template:

<template lang="pug">  
section.section
.content
img.pf__image(
:src="getDynamicFile(post.img)"
:alt="`post-image-${post.id}`"
)
.pf__md(v-html="getDynamicFile(post.file).default")
</template>

As you can see, I dynamically get and render both an image and a .md file using our getDynamicFile mixin.

I think you noticed that I use the v-html directive to render the file, since @nuxtjs/markdownit do the rest. That’s extremely easy!

I can use the ::v-deep selector to customize styles of rendered .md file. Take a look on Github to see how this component is made.

In this component, I only set indents for paragraphs to show the principle of customization, but in a real application, you will need to create a complete set of styles for all used and necessary html elements.

Pages

When all the components are ready, I can create the pages.

As you probably already understood from the design, our application consists of a main page with a list of all posts and a dynamic web page that displays the selected post.

Pages directory structure:

pages/
-- index.vue
-- post/
---- _id.vue

All components are self-contained, and their states are determined through props, so our pages will look like a list of components specified in the right order.

The main page will look like this:

<template lang="pug">  
.page
section-header(
title="Nuxt blog"
subtitle="The best blog you can find on the global internet"
)
post-list
</template>
<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
name: 'HomePage',
})
</script>

To set proper indentation, I used the global .page class I created earlier in assets/styles/global/other.scss.

A separate post page will look a little more complex. Let’s take a look at the script section first:

<script lang="ts">  
import Vue from 'vue'
import { Post } from '~/content/Post'
import posts from '~/content/posts'
export default Vue.extend({
validate({ params }) {
return /^\d+$/.test(params.id)
},
computed: {
currentId(): number {
return Number(this.$route.params.id)
},
currentPost(): Post | undefined {
return posts.find(({ id }) => id === this.currentId)
},
},
})
</script>

I see the validate method. This method is absent in Vue, Nuxt provides it to validate the parameters received from the router. Validate will be called every time you navigate to a new route. In this case, I just check that the id passed to us is a number. If the validation fails, the user will be returned to the error.vue error page.

There are 2 computed properties presented here.
Let’s take a closer look at what they do:

1) currentId - this property returns us the current post id (which was obtained from the router parameters), having previously converted it to number.

2) currentPost returns an object with information about the selected post from the array of all posts.

Well, I seem to figure it out. Let’s take a look at the template:

<template lang="pug">  
.page
link-to-home
section-header(
:title="currentPost.title"
)
post-full(
:post="currentPost"
)
</template>

The style section for this page, as well as for the main page, is missing.

The code for the pages on Github.

Deployment to Hostman

Hooray! Our application is almost ready. It’s time to start deploying it.

To do this task I will use the Hostman cloud platform, which allows to automate the deployment process.

Besides, Hostman provides a free plan for static sites. That is exactly what I need.

To publish I should click the Create button in the platform interface, select a free plan and connect our Github repository, specifying the necessary options for deployment.

Immediately after that, publishing will automatically start and a free domain will be created in the *.hostman.site zone with the ssl certificate from Let's Encrypt.

From now with every new push to the selected branch (master by default) a new version of the application will deploy. Simple and convenient!

Conclusion

I tried to demonstrate in practice how to work with Nuxt.js by building a simple application from start to finish, from making a UI kit to a deployment process.

If you have followed all the steps from this post, congratulations on creating your first Nuxt.js application! Was it difficult? What do you think about this framework? If you have any questions or suggestions, feel free to write comments below.

Sources:

Building

Fonts and Images

Deployment

--

--

--

It's really hard to keep up with all the front-end development news out there. Let us help you. We hand-pick interesting articles related to front-end development. You can also subscribe to our weekly newsletter at http://frontendweekly.co

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Alena Korpula

Alena Korpula

More from Medium

How to use AJax in Laravel

How to develop an HTML and JS source code viewer using PHP

NOTE | Vue — How to redirect to external URL

Choosing the Best JavaScript Framework Between Vue and React