Gregg Pollack
Mar 27 · 11 min read

In this Nuxt tutorial, a free lesson from the Vue Mastery course, we’ll build an application together and learn about the folder structure that Nuxt.js gives us out of the box. If you’re not sure why you might use Nuxt.js, you might check out my previous article on 7 problems you can avoid by using Nuxt.js.

Video version of this article

Prerequisite Knowledge

From this point forward I’m going to assume that you’re comfortable with HTML, CSS, JavaScript basics, Vue.js, Vue Router, and Vuex. If you’re not familiar with these Vue topics, I recommend you stop here and take our Real World Vue.js course and our Mastering Vuex course.

Creating our Initial Nuxt.js App

To get started creating our application we’ll be using create-nuxt-app. Create Nuxt App is a command line tool that helps you create and scaffold out your Nuxt applications. It helps you set up the default folder structure of your app, you can optionally install your preferred server-side framework like Express or Koa, and you can also install the Nuxt Axios module for easy Axios integration with your app.

To get started, make sure you’re using a version of npm that’s 5.2.0 or higher. This will ensure you have npx installed, we’ll need this command.

Then let’s run the following in your command in the terminal:

$ npx create-nuxt-app real-world-nuxt

npx is a tool intended to help round out the experience of using packages from the npm registry — the same way npm makes it easy to install and manage dependencies hosted on the registry, npx makes it easy to use CLI tools and other executables hosted on the registry. In our case, that’s create-nuxt-app.

If Yarn is your package manager of choice you can run yarn create nuxt-app real-world-nuxt. It’ll set you up in the same way.

After running either the npx or yarn command, Create Nuxt App will prompt you with a few questions to get you setup with your app’s default configuration.

Here is how we’re going to answer for our project, and I really encourage you to code with me.

It will also create a git repository for you, and run npm install to fetch all dependencies.

To run the app that was just created in development mode, we’ll need to cd into the directory it created for us run the following command:

$ npm run dev

This will startup our Nuxt.js server in development mode. By default, the project will serve from http://localhost:3000/. Visiting that URL will show you the scaffolded project with a link to Nuxt’s documentation.

This would be a good point to create our first git commit, and add a repo to github (or wherever you like to store your source code).

Now we’re in a good position to start building the events app. Before we get started, let’s go over the folder structure that was created for us by create-nuxt-app.

What Vue (not Nuxt) Would Have Given Us

If we had created just a Vue application using the Vue CLI, it would given us just a /src directory and inside that a components directory. This leaves us with a few questions:

  • Do we put all our components in one /components directory?
  • Do we create a /views directory?
  • Where do our layouts go? You know, the ones with <router-view />?

The Nuxt.js Component Folder Structure

Open the project generated by create-nuxt-app in your code editor of choice, and you’ll see a list of folders where your entire Nuxt application will live. You’ll first notice that Nuxt.js has no /srcdirectory, these folder are in root. Three of the eight folders there are the component folders:

Each of these folders will contain component .vue files, and they each start out with a single default generated file, which together showed us the page we saw when we launched the development server.

Here are the remaining five folders Nuxt generated for us.

/store

A folder to contain all of your app’s Vuex Store Files. Nuxt gives you two different ways to create your store and we’ll dive deeper into how as the course progresses.

/static

Use this directory for storing static assets. For example, robots.txt or your favicon. Every single file in this directory is mapped to the server root, usually at /.

/assets

This directory contains un-compiled assets such as Stylus or Sass files, images, or fonts. By default, Nuxt uses vue-loader, file-loader and url-loader webpack loaders for strong assets serving. If you don’t want assets to be affected by webpack, use the static directory for storing those assets.

/plugins

This folder contains your JavaScript plugins that you want to run before instantiating the root Vue.js Application. This directory is helpful when using your own libraries or Vue plugins.

/middleware

Middleware lets you define custom functions that can be run before rendering either a page or a group of pages (layouts). This folder contains your application middleware for that purpose.

Finally, you’ll notice a configuration file called nuxt.config.js. We’ll get to this in a future lesson. But, for now, know that this is the single file that you’ll be using to write extra configuration, or to modify configuration that Nuxt sets up for your app by default.

Here’s a simple slide showing each of these:

Image Assets Example

Usually we’ll want to place all our images in the assets directory where Nuxt will use vue-loader, file-loader, and url-loader for effective asset serving. For example, if we placed a logo.png file in our assets directory, to use this in a component template we would write:

📜 /pages/index.vue

<img src="~/assets/logo.png">

When we build our project, if our image is >= 1 kb, it will use version hashes for caching and render out:

<img src="/_nuxt/img/82f7965.png">

The 82f7965 is the hash we’re talking about. This is beneficial because if our logo.png file changes in the future, but the name itself remains the same, the hash will change, and thus the new logo will be loaded in our customer’s browsers. Without the change of hash our customer’s browser may continue to load the old image.

When we build our project, if our image is < 1 kb it will inline the image to reduce http requests, looking something like this:

<img src="data:image/png;base64,R0l...">

Obviously where I put it would be a much longer string, which contains all the data for the image. By placing the image code right there on the screen, we avoid an extra network request to fetch the file.

Creating our First Pages & Routes

In a minute we’re going to create our first two pages with links so we can navigate between them. We’ll create an /pages/index.vue where we’ll eventually list our events, and a /pages/create.vuewhere we’ll create an event list. Once we create these do you think we’ll need to create arouter.js?

Nope, Nuxt autogenerates the routes for us. We don’t need to write this code:

const router = new Router({
routes: [
{
path: '/',
component: 'pages/index.vue'
},
{
path: '/create',
component: 'pages/create.vue'
}
]
})

Steps to Build our Example App

We’ll be doing the following step by step:

  1. Fix the settings in VS Code.
  2. Copy/paste some global styles into our /layout/default.vue.
  3. Simplify our /pages/index.vue.
  4. Startup our development server and test.
  5. Create /pages/create.vue.
  6. Create a new component for navigation /component/NavBar.vue.
  7. Use our NavBar component in our /layout/default.vue.

Step 1 — Fixing Settings in VS Code

If you followed along with us in Real World Vue when we setup our VS Code for editing Vue projects, you may run into a conflict between Vetur’s Prettier HTML formatting and the ESLint formatting on our project. If you have the same problem that I did, you’ll want to go into Preferences => Settings => Extensions => Vetur, and change the default HTML formatter from prettyhtml to none, as shown below:

Step 2 — Adding Styles into our /layout/default.vue

To start building our example app, we’re going to change our default layout to the following. Notice that we added an id to our <div> element, and a bunch of our own styles. You also might notice the <nuxt /> tag, which is where our /page components get rendered. It behaves a lot like our <router-view />.

📜 /layout/default.vue

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

<style>
html {
-webkit-text-size-adjust: 100%;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
margin: 0;
font-family: 'Open Sans', sans-serif;
font-size: 16px;
line-height: 1.5;
}
#app {
box-sizing: border-box;
width: 500px;
padding: 0 20px 20px;
margin: 0 auto;
}
hr {
box-sizing: content-box;
height: 0;
overflow: visible;
}
a {
color: #39b982;
font-weight: 600;
background-color: transparent;
}
img {
border-style: none;
width: 100%;
}
h1,
h2,
h3,
h4,
h5,
h6 {
display: flex;
align-items: center;
font-family: 'Montserrat', sans-serif;
}
h1 {
font-size: 50px;
font-weight: 700;
}
h2 {
font-size: 38px;
font-weight: 700;
}
h3 {
font-size: 28px;
font-weight: 700;
}
h4 {
font-size: 21px;
font-weight: 700;
}
h5 {
font-size: 16px;
font-weight: 700;
}
h6 {
font-size: 15px;
font-weight: 700;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 80%;
}
.eyebrow {
font-size: 20px;
}
.-text-primary {
color: #39b982;
}
.-text-base {
color: #000;
}
.-text-error {
color: tomato;
}
.-text-gray {
color: rgba(0, 0, 0, 0.5);
}
.-shadow {
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.13);
}
.badge {
display: inline-flex;
height: 26px;
width: auto;
padding: 0 7px;
margin: 0 5px;
background: transparent;
border-radius: 13px;
font-size: 13px;
font-weight: 400;
line-height: 26px;
}
.badge.-fill-gradient {
background: linear-gradient(to right, #16c0b0, #84cf6a);
color: #fff;
}
button,
label,
input,
optgroup,
select,
textarea {
display: inline-flex;
font-family: 'Open sans', sans-serif;
font-size: 100%;
line-height: 1.15;
margin: 0;
}
button,
input {
overflow: visible;
}
button,
select {
text-transform: none;
}
button,
[type='button'],
[type='reset'],
[type='submit'] {
-webkit-appearance: none;
}
button::-moz-focus-inner,
[type='button']::-moz-focus-inner,
[type='reset']::-moz-focus-inner,
[type='submit']::-moz-focus-inner {
border-style: none;
padding: 0;
}
button:-moz-focusring,
[type='button']:-moz-focusring,
[type='reset']:-moz-focusring,
[type='submit']:-moz-focusring {
outline: 2px solid #39b982;
}
label {
color: rgba(0, 0, 0, 0.5);
font-weight: 700;
}
input,
textarea {
box-sizing: border-box;
border: solid 1px rgba(0, 0, 0, 0.4);
}
textarea {
width: 100%;
overflow: auto;
font-size: 20px;
}
[type='checkbox'],
[type='radio'] {
box-sizing: border-box;
padding: 0;
}
[type='number']::-webkit-inner-spin-button,
[type='number']::-webkit-outer-spin-button {
height: auto;
}
[type='search'] {
-webkit-appearance: textfield;
outline-offset: -2px;
}
[type='search']::-webkit-search-decoration {
-webkit-appearance: none;
}
[type='text'],
[type='number'],
[type='search'],
[type='password'] {
height: 52px;
width: 100%;
padding: 0 10px;
font-size: 20px;
}
[type='text']:focus,
[type='number']:focus,
[type='search']:focus,
[type='password']:focus {
border-color: #39b982;
}
::-webkit-file-upload-button {
-webkit-appearance: button;
font: inherit;
}
[hidden] {
display: none;
}
.error {
border: 1px solid red;
}
select {
width: 100%;
height: 52px;
padding: 0 24px 0 10px;
vertical-align: middle;
background: #fff
url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3E%3Cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E")
no-repeat right 12px center;
background-size: 8px 10px;
border: solid 1px rgba(0, 0, 0, 0.4);
border-radius: 0;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
}
select:focus {
border-color: #39b982;
outline: 0;
}
select:focus::ms-value {
color: #000;
background: #fff;
}
select::ms-expand {
opacity: 0;
}
.field {
margin-bottom: 24px;
}
.error {
border: 1px solid red;
}
.errorMessage {
color: red;
}
</style>

Step 3 — Simplify /pages/index.vue

Next we’ll open our /pages/index.vue file which Vue created for us, delete everything we see there and replace it with:

📜 /pages/index.vue

<template>
<div>
<h1>Events</h1>
</div>
</template>

Step 4 — Startup our Development Server and Test

Now let’s try out our code so far and make sure everything is working. We’ll make sure our development server is running npm run dev, and call up our browser. You should see on http://localhost:3000/

Wonderful!

Step 5 — Create /pages/create.vue

We’re going to create our second page now, with the simple template code:

📜 /pages/create.vue

<template>
<div>
<h1>Create An Event</h1>
</div>
</template>

If we dive into our browser, we can indeed navigate to both pages even though we haven’t created or modified a router.js file. Nuxt.js is doing the routing for us.

Step 6 — Create a NavBar Component for Navigation

Next, we’ll delete the /component/Logo.vue file that the Nuxt generator created for us, and create our own component. Notice that we’re using the nuxt-link to generate our links instead of router-link.

📜 /component/NavBar.vue

<template>
<div class="nav">
<nuxt-link to="/" class="brand">Real World Events</nuxt-link>
<nav>
<nuxt-link to="/">List</nuxt-link>&nbsp;|
<nuxt-link to="/create">Create</nuxt-link>
</nav>
</div>
</template>

<style scoped>
.brand {
font-family: 'Montserrat', sans-serif;
font-weight: 700;
font-size: 1.5em;
color: #39b982;
text-decoration: none;
}
.nav {
display: flex;
justify-content: space-between;
align-items: center;
height: 60px;
}
.nav .nav-item {
box-sizing: border-box;
margin: 0 5px;
color: rgba(0, 0, 0, 0.5);
text-decoration: none;
}
.nav .nav-item.router-link-exact-active {
color: #39b982;
border-bottom: solid 2px #39b982;
}
.nav a {
display: inline-block;
}
</style>

Now we need to use this component inside our layout, just like we would include any other component.

📜 /layouts/default.vue

<template>
<div id="app">
<nav-bar/>
<nuxt/>
</div>
</template>
<script>
import NavBar from "~/components/NavBar.vue";
export default {
components: {
NavBar
}
};
</script>
<style>
...
</style>

Now that we’ve made these changes we can jump into the browser and see our Nuxt application in action.

⏪ To Re-Vue

In this tutorial we learned how to create a Nuxt.js project, about the different directories Nuxt.js creates for you, and how to start building page components and link them together. If you’d like to continue learning Nuxt with us, you might consider subscribing to Vue Mastery’s Scaling Vue with Nuxt course.

Vue Mastery

The ultimate learning resource for Vue.js Developers

Gregg Pollack

Written by

Vue Mastery

The ultimate learning resource for Vue.js Developers

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade