Creating an SPA with Nuxt

In the previous chapters, we created a variety of Nuxt apps in universal mode. These were universal server-side rendered (SSR) apps. This means they are apps that run on both the server and client sides. Nuxt gives us another option for developing single-page apps (SPA), just like we can do with Vue and other SPA frameworks, such as Angular and React. In this chapter, we're going to guide you through how to develop, build, and deploy an SPA in Nuxt and see what makes it differ from the traditional SPAs that are available.

In this chapter, we will cover the following topics:

  • Understanding classic SPAs and Nuxt SPAs
  • Installing a Nuxt SPA
  • Developing a Nuxt SPA
  • Deploying a Nuxt SPA

Let's get started!

Understanding classic SPAs and Nuxt SPAs

An SPA, also referred to as a classic SPA, is an app that loads once on a browser and does not require us to reload and re-render the page throughout the life of the app. This differs from multiple-page applications (MPAs) in which every change and every data exchange with the server requires that we rerender the entire page anew, from the server to the browser.

In a classic/traditional SPA, the HTML that's served to the client is relatively empty. JavaScript will dynamically render the HTML and content once it gets to the client. React, Angular, and Vue are popular choices for creating classic SPAs. However, don't get confused with the Nuxt app in spa mode (let's called it Nuxt SPA), even though Nuxt offers you the option to develop an "SPA" with just one line of configuration, as follows:

// nuxt.config.js
export default {
mode: 'spa'
}

Nuxt's SPA mode simply means that you lose the server-side features of Nuxt and Node.js, as we learned in Chapter 14Using Linters, Formatters, and Deployment Commands, on turning a universal SSR Nuxt app into a static-generated (pre-rendered) Nuxt app. The same goes for the spa-mode Nuxt app when you use the preceding configuration, your spa-mode Nuxt app will become a purely client-side app.

But the spa-mode Nuxt app is quite different from the classic SPA that you create from the Vue CLI, React, or Angular. This is because, after building the app, the pages and routes of your (classic) SPA will be dynamically rendered by JavaScript at runtime. On the other hand, the pages in a spa-mode Nuxt app will be pre-rendered during build time, and the HTML in each page is as "empty" as the classic SPA. This is where things start to get confusing. Let's take a look at the following examples. Let's say you have the following pages and routes in your Vue app:

src
├── favicon.ico
├── index.html
├── components
│ ├── about.vue
│ ├── secured.vue
│ └── ...
└── routes
├── about.js
├── secured.js
└── ...

Your app will be built into the following distribution:

dist
├── favicon.ico
├── index.html
├── css
│ └── ...
└── js
└── ...

Here, you can see that only the index.html, /css/, and /js/ folders are built into the /dust/ folder. This means that the pages and routes of your app will be dynamically rendered by JavaScript at runtime. However, let's say you have the following pages in your spa-mode Nuxt app:

pages
├── about.vue
├── secured.vue
├── ...
└── users
├── about.js
├── index.vu
└── _id.vue

Your app will be built into the following distribution:

dist
├── index.html
├── favicon.ico
├── about
│ └── index.html
├── secured
│ └── index.html
├── users
│ └── index.html
└── ...

As you can see, every page and route of your app is built with an index.html file and placed in the /dust/ folder just like the static site that you generate for a universal SSR Nuxt app. So, here, we can say that the spa-mode Nuxt app you will build and deploy is a "static" SPA, as opposed to the classic SPA, which is "dynamic". Of course, you can still deploy your spa-mode Nuxt app as if it were a universal SSR Nuxt app using the following deployment commands. This will make it "dynamic" at runtime:

$ npm run build
$ npm run start

But deploying a Nuxt SPA app on a Node.js host can be overkill because there must be some good reasons for you to go for a spa-mode Nuxt app and don't want to use a Node.js host for your SPAs. Hence, pre-rendering the Nuxt SPA into a static-generated app (let's call it a static-generated Nuxt SPA) is probably more sensible. You can pre-render your Nuxt SPA with the nuxt export command easily, just like the universal SSR Nuxt app.

This is what this chapter is all about: developing a Nuxt app in spa mode and generating the required static HTML files before deploying them to a static hosting server such as GitHub Pages. So, let's get started by installing and setting up the environment.

Installing a Nuxt SPA

Installing a Nuxt SPA is the same as installing Nuxt universal SSR using the create-nuxt-app scaffolding tool. Let's get started:

  1. Install a Nuxt project via your terminal using the Nuxt scaffolding tool:
$ npx create-nuxt-app <project-name>
  1. Answer the questions that appear and pick the Single Page App option when asked for the Rendering mode:
? Project name
? Project description
//...
? Rendering mode:
Universal (SSR / SSG)
> Single Page App

After the installation is completed, if you inspect the Nuxt config file in your project's root directory, you should see that the mode option was configured as an SPA for you during the installation process:

// nuxt.config.js
export default {
mode: 'spa'
}
  1. Start Nuxt development mode in your terminal:
$ npm run dev

You should see that only the code on the client-side is compiled on your terminal:

✓ Client
Compiled successfully in 1.76s

You will no longer see any code compiled on the server-side that you would normally see for the Nuxt app in universal mode:

✓ Client
Compiled successfully in 2.75s

✓ Server
Compiled successfully in 2.56s

As you can see, it is rather easy to get the spa-mode environment started in Nuxt. You can also set up this spa-mode manually just by adding the spa value to the mode option in the Nuxt config file. Now, let's develop a Nuxt SPA.

Developing a Nuxt SPA

One major important thing to bear in mind when developing a Nuxt SPA is the Nuxt context that's given to the asyncData and fetch methods, will lose their req and res objects because these objects are Node.js HTTP objects. In this section, we'll create a simple user login authentication, which you should already be familiar with. However, this time, we will make it in the Nuxt SPA. We will also create a page for listing users using dynamic routes, as we learned about in Chapter 4,  Adding Views, Routes, and Transitions. Let's get started:

  1. Prepare the following .vue files or just make a copy from the previous chapter, as follows:
-| pages/
---| index.vue
---| about.vue
---| login.vue
---| secret.vue
---| users/
-----| index.vue
-----| _id.vue
  1. Prepare the Vuex store with the store state, mutations, actions, and the index file for handling user login authentication, as follows:
-| store/
---| index.js
---| state.js
---| mutations.js
---| actions.js

As we mentioned in the previous chapter, we will lose the nuxtServerInit action in the store when we generate the Nuxt Universal SSR app statically, so it is the same in the Nuxt SPA we will not have this server action on the client-side. Thus, we will need a client-side nuxtServerInit action to imitate the server-side nuxtServerInit action. We'll learn how to do this next.

Creating the client-side nuxtServerInit action

The methods and properties in these files are the same as those we had in past exercises, except for the nuxtServerInit action:

// store/index.js
const cookie = process.server ? require('cookie') : undefined

export const actions = {
nuxtServerInit ({ commit }, { req }) {
if (
req
&& req.headers
&& req.headers.cookie
&& req.headers.cookie.indexOf('auth') > -1
) {
let auth = cookie.parse(req.headers.cookie)['auth']
commit('setAuth', JSON.parse(auth))
}
}
}

There is no server involved in the Nuxt SPA since nuxtServerInit is called by Nuxt from the server-side only. So, we will need a solution for that. We can use the Node.js js-cookie module to store the authenticated data on the client-side when the user logs in, which makes it the best candidate to replace the server-side cookie. Let's learn how to achieve this:

  1. Install the Node.js js-cookie module via npm:
$ npm i js-cookie
  1. Create a custom method called nuxtClientInit (you can choose any name you like if you wish) in the store actions to retrieve the user data in the cookie. Then, set it back to the desired state for the specified situation when the user refreshes the browser:
// store/index.js
import cookies from 'js-cookie'

export const actions = {
nuxtClientInit ({ commit }, ctx) {
let auth = cookies.get('auth')
if (auth) {
commit('setAuth', JSON.parse(auth))
}
}
}

As you may recall, the store nuxtServerInit action is always called on the server-side when refreshing the page. The same happens with this nuxtClientInit method; it should be called every time on the client-side when refreshing the page. However, it won't be called automatically, so we can use a plugin to call it each time before the Vue root is initiated.

  1. Create a plugin called nuxt-client-init.js in the /plugins/ directory that will call the nuxtClientInit method through the dispatch method in the store:
// plugins/nuxt-client-init.js
export default async (ctx) => {
await ctx.store.dispatch('nuxtClientInit', ctx)
}

Remember that we can access the Nuxt context in plugins before the Vue root is initiated. The store is added to the Nuxt context, so, we can access the store actions and the nuxtClientInit method is what we are interested in here.

  1. Now, add this plugin to the Nuxt config file in order to install the plugin:
// nuxt.config.js
export default {
plugins: [
{ src: '~/plugins/nuxt-client-init.js', mode: 'client' }
]
}

Now, every time you refresh the browser, the nuxtClientInit method will be called and the state will be repopulated by this method before the Vue root is initiated. As you can see, it isn't straightforward to imitate the nuxtClientInit action when we lose the full power of Nuxt as a universal JavasScript app. But if you must go for a Nuxt SPA, then this issue can be resolved with the nuxtClientInit method we just created.

Next, we will create some custom Axios instances using Nuxt plugins. This should be something that you are quite familiar with already. However, being able to create custom Axios instances is useful because you can always fall back to the vanilla version of Axios when you need to, even though we have the Nuxt Axios module as well. So, let's move on!

Creating multiple custom Axios instances with plugins

In this spa-mode exercise, we will need two Axios instances to make API calls to the following addresses:

  • localhost:4000 for user authentication
  • jsonplaceholder.typicode.com for fetching users

We will use the vanilla Axios (https://github.com/axios/axios) as it gives us the flexibility to create multiple instances of Axios with some custom configurations. Let's get started:

  1. Install the vanilla axios via npm:
$ npm i axios
  1. Create an axios instance on the page you need it:
// pages/users/index.vue
const instance = axios.create({
baseURL: '<api-address>',
timeout: <value>,
headers: { '<x-custom-header>': '<value>' }
})

But creating the axios instance directly on a page is not ideal. Ideally, we should be able to extract this instance and reuse it anywhere. Through the Nuxt plugin, we can create the Axios extracted instance. There are two methods we can follow to create them. We'll look at the first method in the next section.

Installing the custom Axios plugin in the Nuxt config file

In previous chapters, you learned that we can create a plugin with the inject method and install the plugin through the Nuxt config file. Besides using the inject method, it is worth knowing that we can also inject a plugin directly into the Nuxt context. Let's take a look at how to do this:

  1. Create an axios-typicode.js file in the /plugins/ directory, import the vanilla axios, and create the instance, as follows:
// plugins/axios-typicode.js
import axios from 'axios'

const instance = axios.create({
baseURL: 'https://jsonplaceholder.typicode.com'
})

export default (ctx, inject) => {
ctx.$axiosTypicode = instance
inject('axiosTypicode', instance)
}

As you can see, after creating the axios instance, we inject the plugin through the Nuxt context (ctx), use the inject method, and then export it.

  1. Install this plugin in the Nuxt config file:
// nuxt.config.js
export default {
plugins: [
{ src: '~/plugins/axios-typicode.js', mode: 'client' }
]
}

You must set the mode option to client because we only need it on the client-side.

  1. You can access this plugin from anywhere you like. In this example, we want to use this plugin on the user index page to fetch the list of users:
// pages/users/index.vue
export default {
async asyncData({ $axiosTypicode }) {
let { data } = await $axiosTypicode.get('/users')
return { users: data }
}
}

In this plugin, we injected the custom axios instance into the Nuxt context (ctx) directly as $axiosTypicode so that we can call it directly by using the JavaScript destructuring assignment syntax to unpack it as $axiosTypicode. We also injected the plugin using the inject method, so we also call this plugin by using ctx.app, as follows:

// pages/users/index.vue
export default {
async asyncData({ app }) {
let { data } = await app.$axiosTypicode.get('/users')
return { users: data }
}
}

It isn't too hard to create a custom Axios plugin, is it? If you install the plugin through the Nuxt config file, this means it is a global JavaScript function and that you can access it from anywhere. But if you don't want to install it as a global plugin, you can skip installing it in the Nuxt config file. This brings us to the second method of creating a Nuxt plugin.

Importing the custom Axios plugin manually

Another method of creating the custom Axios instance does not involve the Nuxt config at all. We can just export the custom instance as a regular JavaScript function and then import it directly into the page where we need it. Let's take a look at how to do this:

  1. Create an axios-api.js file in the /plugins/ directory, import the vanilla axios, and create the instance, as follows:
// plugins/axios-api.js
import axios from 'axios'

export default axios.create({
baseURL: 'http://localhost:4000',
withCredentials: true
})

As you can see, we're no longer using the inject method; instead, we export the instance directly.

  1. Now, we can import it manually when we need it. In this example, we need it in the login action method, as follows:
// store/actions.js
import axios from '~/plugins/axios-api'

async login({ commit }, { username, password }) {
const { data } = await axios.post('/public/users/login', {
username, password })
//...
}

As you can see, we must import this plugin manually because it is not plugged into the Nuxt lifecycle.

  1. Import it and set the Authorization header on this axios instance in the token middleware, as follows:
// middleware/token.js
import axios from '~/plugins/axios-api'

export default async ({ store, error }) => {
//...
axios.defaults.headers.common['Authorization'] = Bearer:
${store.state.auth.token}
}

Even though we have to import the plugin manually when following this method, at least we have extracted the following setting into a plugin that we can reuse wherever it is needed:

{
baseURL: 'http://localhost:4000',
withCredentials: true
}
You can find the code for the Nuxt SPA, along with these two methods, in /chapter-15/frontend/ in this book's GitHub repository.

Once you have created, tested, and linted all the code and files, you are ready to deploy the Nuxt SPA. So, let's get to it!

Deploying a Nuxt SPA

We can deploy a Nuxt SPA just like we can deploy universal SSR Nuxt apps if we have a Node.js runtime server. If we don't, then we can only deploy the SPA as a static site to a static hosting server such as GitHub Pages. You can deploy a static-generated Nuxt SPA as follows:

  1. Make sure that you have set the value to spa in the mode option in the Nuxt config file:
// nuxt.config.js
export default {
mode: 'spa'
}
  1. Make sure you have the following run scripts in the package.json file as well:
{
"scripts": {
"generate": "nuxt generate"
}
}
  1. Run npm run generatejust like you would for the universal SSR Nuxt app. You should see the following output in the terminal:
ℹ Generating output directory: dist/
ℹ Generating pages
✓ Generated /about
✓ Generated /login
✓ Generated /secret
✓ Generated /users
✓ Generated /

In the preceding output, if you navigate to the /dist/ folder inside the project, you will find an index.html file at the root, as well as index.html files in each subfolder, along with the route name. However, you will not find any of these pages in the dynamic routes that were generated, such as /users/1This is because dynamic routes are not generated in spa-mode, as opposed to universal mode.

Also, if you open the index.html file in the /dist/ folder, you will find all the index.html files are exactly the same – just some "empty" HTML elements, similar to the classic SPA. Furthermore, each index.html file does not contain its individual meta information, only the common ones from nuxt.config.js. The meta information of these pages will be hydrated (filled and updated) at runtime. Due to this, it may seem counter-intuitive and "half-baked" for a "static" SPA. On top of that, no static payload is generated. This means that if you navigate to localhost:3000/users on your browser, you will notice that this page is still requesting its data from https://jsonplaceholder.typicode.com/users instead of fetching the data from the payload like the universal SSR Nuxt app does. This is because Nuxt does not generate the static content in spa mode, even though you have set static for the target property in the Nuxt config file. To overcome these issues, we can generate the static content we need from the universal mode.

  1. Change spa to universal for the mode option in the Nuxt config file:
// nuxt.config.js
export default {
mode: 'universal'
}
  1. Run npm run generate so that Nuxt will make the REST API calls to the API to retrieve the users and export their contents to local static payloads. You will see the following output:
ℹ Generating output directory: dist/
ℹ Generating pages with full static mode
✓ Generated /about
✓ Generated /secret
✓ Generated /login
✓ Generated /users
✓ Generated /users/1
✓ Generated /users/2
...
...
✓ Generated /users/10
✓ Generated /

Notice that there is no dynamic routes are generated in the preceding output. If you navigate to the /dist/ folder again, you should see that the /users/ folder now contains multiple folders, each with the owns user ID. Each of these folders contains an index.html file that contains the contents for that specific user. Now, each index.html file contains its own individual meta information and payload generated in /dist/_nuxt/static/.

  1. Change universal back to spa for the mode option in the Nuxt config file:
// nuxt.config.js
export default {
mode: 'spa'
}
  1. Now, run npm run build on your terminal. You should see the following output:
Hash: c36ee9714ee9427ac1ff 
Version: webpack 4.43.0
Time: 5540ms
Built at: 11/07/2020 07:58:09
Asset Size Chunks Chunk Names
../server/client.manifest.json 9.31 KiB [emitted]
LICENSES 617 bytes [emitted]
app.922dbd1.js 57 KiB 0 [emitted]
[immutable] app
commons/app.7236c86.js 182 KiB 1 [emitted]
[immutable] commons/app
pages/about.75fcd06.js 667 bytes 2 [emitted]
[immutable] pages/about
pages/index.76b5c20.js 784 bytes 3 [emitted]
[immutable] pages/index
pages/login.09e509e.js 3.14 KiB 4 [emitted]
[immutable] pages/login
pages/secured.f086299.js 1.36 KiB 5 [emitted]
[immutable] pages/secured
pages/users/_id.e1c568c.js 1.69 KiB 6 [emitted]
[immutable] pages/users/_id
pages/users/index.b3e7aa8.js 1.5 KiB 7 [emitted]
[immutable] pages/users/index
runtime.266b4bf.js 2.47 KiB 8 [emitted]
[immutable] runtime
+ 1 hidden asset
Entrypoint app = runtime.266b4bf.js commons/app.7236c86.js app.922dbd1.js
ℹ Ready to run nuxt generate
  1. Ignore the Ready to run nuxt generate message. Instead, test your production static SPA from the /dist/ directory first with the nuxt start command on your terminal:
$ npm run start

You should get the following output:

Nuxt.js @ v2.14.0

> Environment: production
> Rendering: client-side
> Target: static
Listening: http://localhost:3000/

ℹ Serving static application from dist/

Now, routes such as localhost:3000/users will no longer request their data from https://jsonplaceholder.typicode.com. Instead, they will fetch the data from the payload in the /static/ folder, which can be found inside the /dist/ folder.

  1. Finally, just deploy this /dist/ directory only to your static hosting server.

If you are looking for a free static hosting server, consider using GitHub Pages. Using this, you can have a domain name for your site in the following format:

<username>.github.io/<app-name>

GitHub also allows you to serve your site with a custom domain name rather than using theirs. For more information, follow the guide from the GitHub Help site: https://help.github.com/en/github/working-with-github-pages/configuring-a-custom-domain-for-your-github-pages-site. However, in this book, we will show you how to serve your site on the GitHub's github.io domain name. We'll learn how to do this in the next section.

You can find the code for this section in /chapter-15/frontend/ in this book's GitHub repository.

Deploying to GitHub Pages

GitHub Pages is a static site hosting service from GitHub that hosts and publishes the static files (HTML, CSS, and JavaScript only) in your GitHub repository. You can get your static site hosted in GitHub Pages as long as you have a user account on GitHub and a GitHub repository created for your site.

Please visit https://guides.github.com/features/pages/ to find out how to get started with GitHub Pages.

You just need to go to the Settings section on your GitHub repository and scroll down to the GitHub Pages section. Then, you need to click the Choose a theme button to start the process of creating your static site.

Deploying the static version of your Nuxt SPA to GitHub Pages is fairly easy – you just need to make some minor configuration changes to the Nuxt config file and then use the git push command to upload it to your GitHub repository. When you create a GitHub repository and if you are creating GitHub Pages, the URL of the static pages, by default, will be served in the following format:

<username>.github.io/<repository-name>

So, you will need to add this <repository-name> to the router base option in the Nuxt config file, as follows:

export default {
router: {
base: '/<repository-name>/'
}
}

But changing the base name will interfere with localhost:3000 when developing the Nuxt app. Let's learn how to resolve this:

  1. Create an if condition for the development and production GitHub Pages in the Nuxt config file, as follows:
// nuxt.config.js
const routerBase = process.env.DEPLOY_ENV === 'GH_PAGES' ? {
router: {
base: '/<repository-name>/'
}
} : {}

This condition simply adds /<repository-name>/ to the base key of the router option if the DEPLOY_ENV option has GH_PAGES in the process environment.

  1. Add this routerBase constant to your Nuxt configuration in the config file using the spread operator:
// nuxt.config.js
export default {
...routerBase
}
  1. Set the DEPLOY_ENV='GH_PAGES' scripts in the package.json file:
// package.json
"scripts": {
"build:gh-pages": "DEPLOY_ENV=GH_PAGES nuxt build",
"generate:gh-pages": "DEPLOY_ENV=GH_PAGES nuxt generate"
}

Using one of these two npm scripts, the value of /<repository-name>/ won't be injected into your Nuxt configuration and interfere with the dev process when running npm run dev for development.

  1. Change spa to universal for the mode option in the Nuxt config file, just as in step 4 in the previous section, to generate the static payloads and pages with the nuxt generate command:
$ npm run generate:gh-pages
  1. Change universal back to spa for the mode option in the Nuxt config file, just as in step 6 in the previous section, to build the SPA with the nuxt build command:
$ npm run build:gh-pages
  1. Push the files in the /dist/ folder that were generated by Nuxt to GitHub Pages through your GitHub repository.

That's it for deploying a Nuxt SPA to GitHub Pages. However, make sure you have an empty .nojekyll file included in the /dist/ folder when pushing your static site to GitHub Pages.

Jekyll is a simple, blog-aware, static site generator. It transforms plain text into static websites and blogs. GitHub Pages is powered by Jekyll behind the scenes and, by default, it does not build any files or directories that start with a dot ".", or an underscore "_", or end with a tilde "~". This will be a problem when serving the static site in GitHub Pages because a subfolder called _nuxt is also generated inside the /dist/ folder when building the Nuxt SPA; this /_nuxt/ folder will be ignored by Jekyll. To fix this, we need to include an empty .nojekyll file in the /dist/ folder to turn off Jekyll. This file is generated when we build the static pages for the Nuxt SPA, so make sure to push it to your GitHub repository as well.

Well done  you have made it through another short chapter of this book! Nuxt SPAs are a great option if you want to build an SPA in Nuxt instead of using Vue or other frameworks such as Angular and React. However, if you are offering web services such as social media sites that require immediate or real-time publications, a static-generated Nuxt SPA probably isn't a good choice. It all depends on the nature of your business and whether you want to go for the full power of Nuxt, the universal SSR, or the client-side only version of Nuxt - Nuxt SPA. Next, we'll summarize what we have learned in this chapter.

Summary

In this chapter, we have learned how to develop, build, and deploy an SPA in Nuxt and see what makes it different from a classic SPA. We also learned that Nuxt SPAs can be a good option for developing apps, but developing a Nuxt SPA means that we will lose the nuxtServerInit action and the req and res HTTP objects. However, we can use the client-side js-cookies (or localStorage) and the Nuxt plugin to imitate the nuxtServerInit action. Last but not least, we learned how to publish and serve the static-generated Nuxt SPA on GitHub Pages.

So far in this book, we have only been using JavaScript for all our Nuxt apps and APIs. However, in the coming chapters, we will explore how we can take Nuxt further so that we can work with another language, PHP. We will walk you through the HTTP messages and PHP standards, writing CRUD operations with PHP database frameworks, and serving a PHP API for the Nuxt app. Read on!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.138.223