Automate your Nuxt.js app deployment
Fullstack Developer and Tech Author
DevOps practices that include continuous integration and continuous deployment are an essential part of modern-day software development. Vue.js developers building server-side rendered applications with Nuxt.js should be taking advantage of these modern processes to deploy their applications. This article will show Vue.js developers using Nuxt.js how to build, test, and deploy their applications to GitHub Pages using CircleCI.
Prerequisites
To follow along with this post, you will need a few things:
Creating a Nuxt.js project and connecting it to GitHub
The first task is to create a new Nuxt.js application and connect it to your GitHub account. Go ahead and create a new Nuxt.js app by running the following command:
npx create-nuxt-app my-nuxt-app
Accept the default project name and project description. The interactive project creation process then continues by asking the following questions. Select the corresponding answers to follow along with this article:
- Use a custom server framework? (Answer: none)
- Choose features to install? (Answer: click Enter to skip all)
- Use a custom UI framework? (Answer: none)
- Use a test framework? (Answer: Jest)
- Choose rendering mode? (Answer: Universal)
- Author? (Answer: click Enter to choose the default)
- Choose a package manager? (Answer: npm)
- Choose Nuxt.js modules? (Answer: click Enter to skip all)
- Choose linting tools? (Answer: click Enter to skip all)
- Choose development tools? (Answer: click Enter to skip all)
Note: Your CLI may display the questions differently but the purpose is the same. You will find it easy to match the questions to the answers above.
After selecting all these preferences, create-nuxt-app
begins scaffolding the new Nuxt.js application. Once the scaffolding is done, the next step is to connect the application to a GitHub repository. Go to your GitHub account and create a new repository. For ease in following along with this tutorial, give the repo the same name as the project, i.e. my-nuxt-app
.
Now, connect the project to the GitHub repo you just created. Go to the root of your Nuxt.js project and run the following commands to make an initial commit:
git add .
git commit -m “First Commit”
Then connect the project to the repo by running the following commands:
git remote add origin https://github.com/coderonfleek/my-nuxt-app.git
git push -u origin master
Note: The GitHub URL for the repository above will not be the same as yours, replace this with yours in order to make your first push and setup tracking between the local and remote master branches.
And that’s it, you now have your project connected to your GitHub repo.
Adding Tests
Based on your selection of Jest as the testing framework, create-nuxt-app
set up all that is needed (in terms of the packages required and the default test configuration all found in the jest.config.js
file) for testing the application. The tests can be found in the test
folder at the root of the application. In the test
folder, we see a Logo.spec.js
test file which contains a simple test for the Logo component that simply checks if the component is a Vue instance. We will be adding another test that checks if the component renders properly. Go ahead and replace the code in Logo.spec.js
with the code below:
import { mount, shallowMount } from "@vue/test-utils";
import Logo from "@/components/Logo.vue";
const factory = () => {
return shallowMount(Logo, {});
};
describe("Logo", () => {
test("is a Vue instance", () => {
const wrapper = mount(Logo);
expect(wrapper.isVueInstance()).toBeTruthy();
});
test("renders properly", () => {
const wrapper = factory();
expect(wrapper.html()).toMatchSnapshot();
});
});
Now, save this file and try out the tests by running the following command:
npm run test
This should display a screen similar to the one below:
Setting up the build script
In order to deploy a server-side rendered Nuxt.js application to a static hosting service like GitHub Pages, we need to run the nuxt generate
command which generates a dist
folder at the root of our project containing a production version of our application. This generated version contains only static files which makes it suitable for a static host.
Now, one thing to recognize about applications deployed to GitHub Pages is that their base URL is the name of the repository for that application. For example, the resultant homepage for the my-nuxt-app
project on GitHub Pages would be https://[YOUR_GITHUB_USERNAME].github.io/my-nuxt-app.
So, we need to find a way to instruct our Nuxt.js application to generate the distribution version with the appropriate router base while deploying to GitHub Pages. If this is not done, all of our routes will fail.
The first thing we need to do is to write a nuxt generate
script that specifically targets the deployment to GitHub Pages. Add the following line as an extra script to the scripts
section in your package.json
file.
scripts : {
...
"generate:gh-pages": "DEPLOY_ENV=GH_PAGES nuxt generate --fail-on-page-error"
}
The generate:gh-pages
script sets the DEPLOY_ENV
environment variable (which we will reference later) to GH_PAGES
(the identity we will use for GitHub Pages) and also includes the fail-on-page-error
flag that ensures that the build fails if there is a page error.
Next, we go into the nuxt.config.js
configuration file and set our router base. Just before the module.exports
line, add the following:
const routerBase =
process.env.DEPLOY_ENV === "GH_PAGES"
? {
router: {
base: "/my-nuxt-app/"
}
}
: {};
Here, we create a routerBase
object which, based on the value of the variable DEPLOY_ENV
, is set to a router base object targeting GitHub Pages if we are deploying to GitHub Pages, and if not, defaults to an empty object. This will ensure that the router base is appropriately set for our project. Make sure to change the my-nuxt-app
value to that of your repo if you are using a different slug.
To finalize our configuration, add the following line inside the exported object in the nuxt.config.js
file:
...
module.exports = {
...routerBase,
/* Other parts of module.exports*/
}
This will spread (pun intended) the routerBase
object into the configuration.
Note: Your nuxt.config.js
file might contain a different export format in the form:
export default {
...
}
This is totally valid and the routerBase
will be included in the exported object since it was included in the module.exports
format above.
Writing the CircleCI deployment script
Now to the main action where we get to set up our deployment script to deploy to GitHub Pages.
This is what we want our script to achieve:
- Install necessary packages
- Run the tests
- Build the project
- Deploy to GitHub Pages
First, let’s create our CircleCI configuration file. Go into the root of your project and create a folder named .circleci
and add a file inside this folder named config.yml
.
Installing the necessary packages
The node_modules
folder is not pushed to the project repo so we need to install the necessary packages for our CircleCI build. Inside the config.yml
file, enter the configuration below:
version: 2.1
jobs:
build:
working_directory: ~/repo
docker:
- image: circleci/node:10.16.3
steps:
- checkout
- run:
name: update-npm
command: "sudo npm install -g npm@5"
- restore_cache:
key: dependency-cache-{{ checksum "package-lock.json" }}
- run:
name: install-packages
command: npm install
- save_cache:
key: dependency-cache-{{ checksum "package-lock.json" }}
paths:
- ./node_modules
In the configuration above, we create a build
job that uses a Node Docker image from the CircleCI registry. We then check out our code and update npm. Next, we install our dependencies by running the npm install
command and create a cache of our node_modules
folder using the save_cache
step. We also add a restore_cache
step to restore our cached assets for use after we have saved a cache in a previous run.
Run the tests
The next thing to add to our configuration is a step to run our tests. We do that by adding another run
step as shown below:
...
- run:
name: test
command: npm run test
This step runs the npm run test
command that we ran locally earlier to run our tests using Jest.
Build the project
We now go ahead and build our project by running the nuxt generate
command with another step as shown below:
...
- run:
name: build-project
command: npm run generate:gh-pages
Note: This step specifically calls the generate:gh-pages
script which has been designed for deployment to GitHub Pages.
Deploy to GitHub Pages
Now, to our final task which is to deploy to GitHub Pages. To keep things neat, we would like to keep our deploy branch away from the master
branch. This deploy branch will be maintained as an orphan branch solely for deployment to GitHub Pages. That could be a very tedious process, but lucky for us there is a Node.js package called gh-pages that can help us achieve that. This package will help us push our files to a special gh-pages
branch on our repo which will then be deployed to GitHub Pages.
Add the following deploy steps to your config.yml
file:
- run:
name: Install and configure dependencies
command: |
npm install gh-pages --save-dev
git config user.email "fikfikky@gmail.com"
git config user.name "coderonfleek"
- run:
name: Deploy docs to gh-pages branch
command: './node_modules/.bin/gh-pages --dotfiles --message "[skip ci] Updates" -d dist'
- store_artifacts:
path: test-results.xml
prefix: tests
- store_test_results:
path: test-results.xml
In the first step above, we install the gh-pages
package and set our git
configurations for GitHub in our container. Make sure to change the values of user.email
and user.name
to your GitHub account details.
In the next step, we use the local installation of our gh-pages
package to publish everything in our generated dist
folder from the preceding build step to our gh-pages
branch.
Notice that we added a few arguments to our gh-pages
command. We provided a special commit message (passed with –message) which contains [skip ci]
. This instructs CircleCI not to initiate a new build when we push these contents to the gh-pages
branch. We also added --dotfiles
so that the gh-pages
command will ignore all dotfiles.
The complete configuration file is shown below:
version: 2.1
jobs:
build:
working_directory: ~/repo
docker:
- image: circleci/node:10.16.3
steps:
- checkout
- run:
name: update-npm
command: "sudo npm install -g npm@5"
- restore_cache:
key: dependency-cache-{{ checksum "package-lock.json" }}
- run:
name: install-packages
command: npm install
- save_cache:
key: dependency-cache-{{ checksum "package-lock.json" }}
paths:
- ./node_modules
- run:
name: test
command: npm run test
- run:
name: build-project
command: npm run generate:gh-pages
- run:
name: Install and configure dependencies
command: |
npm install gh-pages --save-dev
git config user.email "fikfikky@gmail.com"
git config user.name "coderonfleek"
- run:
name: Deploy docs to gh-pages branch
command: './node_modules/.bin/gh-pages --dotfiles --message "[skip ci] Updates" -d dist'
- store_artifacts:
path: test-results.xml
prefix: tests
- store_test_results:
path: test-results.xml
Commit your changes and push them to the master branch.
Our first deployment
Now let’s take our deployment for a test drive.
First, we need to connect our project to CircleCI. So head to your CircleCI dashboard and add the project in the Add Project section. Next to your project, click Set Up Project.
This will bring you to a page similar to the one below:
Click Start building to begin building your project.
CircleCI will then run your configuration to build, test, and deploy your project.
Well, you should have notice THE BUILD FAILS!
Why did that happen? If you click on the process tab and inspect every step of the build process, you will notice that the deployment failed at the point where gh-pages
was going to push our files to the deploy branch.
Ok, let me confess. I knew it was going to fail. My apologies. :)
The reason why this failed is that our connection to GitHub, needed to push our files, was not authenticated.
To make an authenticated connection to GitHub, we need a deployment key in the form of a private/public SSH key pair that is saved in our GitHub account and given write access.
Setting up authentication to GitHub
To create an SSH key pair on our local machine, simply run the following command:
ssh-keygen -t rsa -b 4096 -C “YOUR_GITHUB_EMAIL”
Make sure to replace YOUR_GITHUB_EMAIL
with the email address for your GitHub account.
Chose a destination name like my_deploy_key
and accept the default of no password by hitting Enter at the prompt.
This should automatically generate a public/private key pair for you at the chosen destination named my_deploy_key
(private key) and my_deploy_key.pub
(public key).
The private key is to be saved within CircleCI. Go to your CircleCI Pipelines page and click on the button with the cog icon to access the Settings page.
On the side-menu of the settings page, scroll down and click SSH Permissions:
Then click Add SSH Key to add a new key:
In the hostname field, simply enter “github.com” and paste the contents of your private key into the Private Key field and click Add SSH Key to add the key.
Learn more about adding your private key to CircleCI in the documentation.
Once added, you will see the fingerprint of your newly added key. CircleCI uses fingerprints to identify private keys in its scripts as its safer than exposing the actual private key.
We also need to ensure that the “Pass secrets to builds from forked pull requests” option on our Advanced Settings page is set to Off. The Advanced Settings link can be found on the side menu of our project settings page.
To finalize our security checks, ensure that you delete the private key from your local system.
The next thing we need to do is to submit our public key to our GitHub account. To do this, go to the Settings tab of your GitHub project repo. Then click Deploy Keys on the side menu. On the Deploy Keys page, click Add deploy key and the form below shows up.
In the Title field, give it a convenient name, e.g. “My CircleCI Deployment Key”. In the Key field, paste in the contents of your public key (my_deploy_key.pub
) and check the Allow write access checkbox. Then click Add key.
GitHub might ask you to confirm your password, confirm it and you will see your key displayed in the list of deploy keys.
One last thing we need to do is to add the fingerprint of our private key from CircleCI to our configuration file. Our deploy steps now look like this:
- run:
name: Install and configure dependencies
command: |
npm install gh-pages --save-dev
git config user.email "fikfikky@gmail.com"
git config user.name "coderonfleek"
- add_ssh_keys:
fingerprints:
- "3a:9b:c5:67:6f:06:50:55:dd:67:c9:ed:0c:9e:1f:fa"
- run:
name: Deploy docs to gh-pages branch
command: './node_modules/.bin/gh-pages --dotfiles --message "[skip ci] Updates" -d dist'
- store_artifacts:
path: test-results.xml
prefix: tests
- store_test_results:
path: test-results.xml
Note: Change the fingerprint used here to your own fingerprint.
The entire configuration file should now look like this:
version: 2.1
jobs:
build:
working_directory: ~/repo
docker:
- image: circleci/node:10.16.3
steps:
- checkout
- run:
name: update-npm
command: "sudo npm install -g npm@5"
- restore_cache:
key: dependency-cache-{{ checksum "package-lock.json" }}
- run:
name: install-packages
command: npm install
- save_cache:
key: dependency-cache-{{ checksum "package-lock.json" }}
paths:
- ./node_modules
- run:
name: test
command: npm run test
- run:
name: build-project
command: npm run generate:gh-pages
- run:
name: Install and configure dependencies
command: |
npm install gh-pages --save-dev
git config user.email "fikfikky@gmail.com"
git config user.name "coderonfleek"
- add_ssh_keys:
fingerprints:
- "3a:9b:c5:67:6f:06:50:55:dd:67:c9:ed:0c:9e:1f:fa"
- run:
name: Deploy docs to gh-pages branch
command: './node_modules/.bin/gh-pages --dotfiles --message "[skip ci] Updates" -d dist'
- store_artifacts:
path: test-results.xml
prefix: tests
- store_test_results:
path: test-results.xml
Successful deployment of our Nuxt.js application
Now, let’s commit our changes and push to the master branch once again to trigger the deployment.
Once you push, a new build process will be triggered and this time it will be successful.
Click the deployment tab and check the test section. You will notice that all of our tests are also running fine.
To confirm that our application is deployed, visit the URL https://[YOUR_GITHUB_USERNAME].github.io/my-nuxt-app and view the deployed application. You should see the default Nuxt.js index page as displayed below:
Conclusion
In this article, we have seen how continuous integration and continuous delivery can be used to successfully deploy a Nuxt.js application to GitHub Pages. With CircleCI’s exhaustive list of configuration options, we could fine-tune our configuration to suit our specific needs and deploy to other hosting services, too.
I hope you had as much fun as I had on this project. Happy coding :)