Contents

    Guides

    An ultimate guide to CICD for your Nuxt.js Application

    David Adeneye

    David is a developer & technical writer. When he is not writing code/content, he is reading on how to design and develop good software products.

    Published on

    August 11, 2022
    An ultimate guide to CICD for your Nuxt.js Application

    Nuxt is a free and open-source JavaScript library built on top of Vue.js. Among other powerful features, it enables developers to build server-side rendered(SSR) applications.  In today's modern software development, DevOps practices such as Continuous Integration(CI) and Continuous Deployment(CD) are essential for automating the software delivery process. These modern processes can help developers building SSR applications with Nuxt.js automate their application deployments seamlessly.

    This tutorial will demonstrate how to build, test, and deploy your Nuxt.js Application to GitHub Pages using CircleCI. GitHub Pages is a free static site hosting platform for hosting static web pages via Git repositories. There are several other hosting platforms such as Netlify and Vercel, however, in this tutorial, we will use GitHub Pages for our demo. CircleCI is a continuous integration and continuous delivery platform for implementing DevOps practices. There are other CI/CD(Continous Integration/Delivery&Deployment) platforms such as Jenkins and Travis CI.

    Prerequisites

    To follow along with this tutorial, you will need the following:

    • Basic knowledge of JavaScrip and Nuxt
    • A CircleCI account
    • A GitHub account
    • Node installed on your system

    What is the CI/CD pipeline?

    CI/CD pipeline is a DevOps mechanism for automating the process of software delivery. It involves the continuous automation, integration, and testing phase to delivery and deployment throughout the lifecycle of a software product. CI/CD pipeline helps automate the process of software delivery and enables you to perform build and test automatically. It also helps to improve flexibility with the ability to ship new functionalities faster. By utilizing the CICD pipeline, you can reduce manual errors and provide feedback to software developers, allowing for faster product iteration.

    Difference between Continuous Integration, Continuous Delivery & Continuous Deployment

    Continuous Integration, Continuous Delivery, and Continuous Deployment are common terms in modern software development practice and the DevOps ecosystem. Often, these terms are used interchangeably and as synonyms. Despite each being part of the software delivery process, each approach differs in its implementation and what they mean to a development team.

    • Continuous Integration is a software development practice that involves developers integrating and merging changes to the code base as frequently as possible into the main branch. These changes are validated by creating a build and running automated tests against the build. If these tests fail, the changes are not merged, which provides developers with an opportunity to avoid integration challenges. By implementing continuous Integration, fewer bugs are shipped to production as the issues are detected early, and integration issues are addressed before the release.
    • Continuous Delivery extends continuous Integration. It's a software development approach that automatically deploys all code changes to the testing and/or production environment after the changes have been merged. In addition to the automated testing process, there is also an automated release process, which means that developers can deploy changes at any time with the click of a button or after completing the CI process.
    • Continuous Deployment takes the process further in contrast to Continuous Delivery. With this approach, product functionalities are delivered through automatic deployment. During each stage in the pipeline, all changes that pass verification are released to production. This process is fully automated, and a new change can only be deployed to production if it passes all tests.
    • Continuous Deployment is a great way to accelerate the feedback loop with your customers and relieve pressure on the team. It allows developers to concentrate on building software and see their work go live minutes after they've finished working on it.

    CICD pipelines consist of four important stages. Any developer developing a new version of any software should follow the following stages. In the event of a failure at any stage, developers receive feedback notifications.

    • Source Stage
    • Build Stage
    • Test Stage
    • Deploy Stage

    Setting up your Nuxt.js project

    We need to set up a Nuxt.js application and connect it to GitHub to get started. Run the following command to create a new Nuxt.js app:

    npx create-nuxt-app nuxt-cicd

    After the command, proceed and press Yes. It will prompt an interactive project creation process and ask some questions. Some of these Nuxt CLI implementation questions might change in the future, however, these are the  three important questions and answers for this tutorial:

    • Testing Framework?: Select Jest
    • Continuous Integration?: Select CircleCI
    • Version Control System?: Select Git

    After answering all these questions, create-nuxt-app will begin scaffolding the new Nuxt.js application. As soon as the scaffolding is complete, you will need to connect your app to a GitHub repository. Go to your GitHub account and create a new repository. Let the name of your repo be the same as your project, in my case, i.e., nuxt-cicd. After that, connect the project to the GitHub repo you just created. Navigate into the root folder of your Nuxt.js project and run the following command to make an initial commit.

    git add .
    git commit -m “First Commit”
    

    Then connect your project to the repo by running the following command:

    git remote add origin https://github.com/daacode/nuxt-cicd.git
    git branch -M main
    git push -u origin main
    

    Remember to replace the repository GitHub URL above with yours. With this, your project will be connected to your GitHub repo.

    Setting Up Tests with Jest

    While setting up our Nuxt project, we chose Jest as our testing framework. The create-nuxt-app has set up what we needed for testing our application (including the packages required and the default test configuration)  along with a jest.config.js file. The test folder is located at the root of the application. Inside the test folder, you will see a default NuxtLogo.spec.js test file with a test for the Nuxt Logo component that simply checks if the component is a Vue instance. Additionally, we will add another test to check if the Nuxt logo components render properly. Replace the NuxtLogos.spec.js file with the following code below:

    import { mount, shallowMount } from '@vue/test-utils'
    import NuxtLogo from '@/components/NuxtLogo.vue'
    
    
    describe('NuxtLogo', () => {
      test('is a Vue instance', () => {
        const wrapper = mount(NuxtLogo)
        expect(wrapper.vm).toBeTruthy()
      });
    
      test("NuxtLogo renders properly", () => {
        const wrapper = shallowMount(NuxtLogo, {})
        expect(wrapper.html()).toMatchSnapshot();
      });
    })
    

    After that, save the file and run the tests with the following command:

    npm run test

    If your test runs successfully, your display screen should be similar to this below:

    Screenshot of display screen after a successful test run.
    Test result

    Setting up the build and deployment script

    Next, we need to run the nuxt generate command to create a dist folder at the root of our Nuxt project. This folder will contain a production version of the application to be deployed to a static hosting platform like GitHub Pages. The generated version contains only static files, making it suitable for static hosting

    One thing to note about applications deployed to GitHub Pages is that their base URLs are the name of your application's repository. For instance, your URL for the nuxt-cicd application project on GitHub Pages would be https://[YOUR_GITHUB_USERNAME].github.io/nuxt-cicd.

    We need to inform our Nuxt application to generate the distribution version and the proper router base when deploying to GitHub Pages. This will cause all of our routes to fail if we don't do it.

    The first step is writing a nuxt generate script that specifically targets the deployment to GitHub Pages. Add the following extra script in the script section in your package.json file.

    scripts : {
        ...
        "generate:gh-pages": "DEPLOY_ENV=GH_PAGES nuxt generate --fail-on-page-error"
    }

    In the script above, the generate: gh-pages script sets the DEPLOY_ENV environment variable (which we will reference later in the tutorial) to GH_PAGES (we will use this identity for GitHub Pages) and also add the fail-on-page-error flag that confirms that the build fails if there is a page error.

    Then, we need to set our router base inside the nuxt.config.js configuration file. To do that, add the following code just before the module.exports line:

    const routerBase = 
        process.env.DEPLOY_ENV === "GH_PAGES"
            ? {
                  router: {
                  base: "/nuxt-cicd/"
                  }
              }
            : {};
    

    We created a routerBase object based on the variable DEPLOY_ENV value in the code above. The routerBase value is set to a router base object targeting GitHub pages if we deploy our application to GitHub pages. Otherwise, the default value should be an empty object. By doing so, we'll ensure that the router base is properly configured for our project. Dont forget to change the base value of: nuxt-cicd if you're using a different slug for your repo.

    To complete our configuration, we need to add the following line within the exported object in the nuxt.config.js file:

    export default {
        ...routerBase
        ...	
    }

    Setting up CircleCI deployment Script

    As part of setting up the deployment script to deploy to GitHub pages, we need a script that achieves the following:

    • To install necessary packages
    • To run the tests
    • To build the projects
    • To deploy to GitHub pages

    The first thing we need to do is to create a CircleCI configuration. To do that, create a folder named .circleci in the root folder of your project. Then add a file named config.yml inside the folder.

    Installing Packages

    Because the node_modules folder isn't pushed to the project repository, we must install the necessary packages for our CircleCI build. Enter the following configuration below inside the config.yml file:

    version: 2.1
    jobs:
      build:
        working_directory: ~/repo
        docker:
          - image: cimg/node:12.16.0
        steps:
          - checkout
          - run:
              name: update-npm
              command: "sudo npm install -g npm@6"
          - 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
    

    As shown in the configuration above, we created a build job that utilizes the Node Docker image available in CircleCI registry. The cimg/node 12.16.0 is a Docker image created by CircleCI to support continuous integration. The tag 12.16.0 implies the version of Node.js will be Node.js v12.16.0; however, your node version might be different.

    After that, we check out our code and update npm. Then, we install our dependencies by running the npm install command and creating a cache of our node_modules folder using the save_cache step. Also, we add a restore_cache step to restore our cached assets for use after saving a cache in a previous run.

    Running the tests

    Next, we need to add a step to run our tests in our configuration. To do that, add another run step :

    - run:
        name: test
        command: npm run test
    

    The step will run the npm run test command that we previously ran locally to run our tests using Jest.

    Build the project

    We will add another step to build our project, as shown below, by running the nuxt generate command.

    - run:
        name: build-project
        command: npm run generate:gh-pages
    

    This step above invokes the `generate: gh-pages` script that was designed for deployment to GitHub pages.

    - run:
        name: test
        command: npm run test
    

    The step will run the npm run test command that we previously ran locally to run our tests using Jest.

    Deploying to GitHub Pages

    We need to add the last script for deploying to GitHub pages. Before that, we would need to separate our deploy branch away from the main branch. This separate branch will be maintained exclusively for deployments to GitHub pages. This could be a time-consuming procedure but, fortunately, there is a Node.js package called gh-pages that can help us accomplish that. With this package, we will be able to push our files to a special gh-pages branch on our repository and have them deployed to GitHub Pages.

    To do that, 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 "YOUR_EMAIL_ADDRESS"
            git config user.name "YOUR_GITHUB_USERNAME"
    
    - 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
    

    We install the gh-pages package in the configuration above and set our git configurations for GitHub in our container. Remember to change the values of both user.email and user.name to your own GitHub account details.

    In the second step, we use the local installation of our gh-pages package to publish everything in our generated dist folder from the previous build step to our gh-pages branch.

    You will notice that we added a few arguments to the gh-pages command. We also assigned a special commit message (prefixed with --message) which contains [skip ci]. This command will instruct CircleCI not to initiate a new build when pushing these contents to the gh-pages branch. Lastly, we assigned --dotfiles to the gh-pages command so that it will ignore all dotfiles.

    Here is the complete configuration file:

    version: 2.1
    jobs:
      build:
        working_directory: ~/repo
        docker:
          - image: cimg/node:12.16.0
        steps:
          - checkout
          - run:
              name: update-npm
              command: "sudo npm install -g npm@6"
          - 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 "youremail@gmail.com"
                git config user.name "daacode"
          
          - 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
    

    Then commit your changes and push them to the main branch.

    Checking Our Deployment

    As a first step, we need to connect our project with CircleCI. Go to your CircleCI dashboard (remember we highlighted creating an account at the beginning of this article), and add the project in the Add Project section. Click the Set Up project next to your project. The display should be similar to this below:

    Set up project screen for cicd nuxtjs
    Adding Project

    Click the Set Up Project to start building the project. Then CircleCI will run the configuration to build, test, and deploy your application. You will notice the build fails. Dont panic; this was expected. To see why the build fails, click on the process tag and inspect every step of the build process. You will discover that the deployment failed at the stage where gh-pages would push our files to the deploy branch. This error occurred because our connection to GitHub, which needed to push our files, was not authenticated.

    Screen with error message for failed build
    Failed Build

    To establish an authenticated connection with GitHub, we need a deployment key in the form of a private/public SSH key pair that is served in our GitHub account and granted write access.

    Setting up authentication to GitHub

    Run the following command to create an SSH key pair on our local machine :

    ssh-keygen -t rsa -b 4096 -C “ YOUR_GITHUB_EMAIL”

    Remember to replace the YOUR_GITHUB_EMAIL with the email address you used for your GitHub account. Then choose a destination name like deploy_key and press Enter to accept the default password of none. Afterward, this should automatically generate a public/private key pair for you at the chosen destination named deploy_key (private key) and deploy_key.pub (public key).

    After that, you have to save the private key within CircleCI. Navigate 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 Project Setting Page, scroll down and click SSH Keys:

    Image of Project Setting's Page sidebar
    SSH Keys

    It will prompt a modal box to Add an SSH Key:

    Screenshot of prompt modal box to enter an SSH Key
    Add SSH Keys

    The form has two input fields. Enter "github.com" as the hostname, and paste the contents of your "private key()" into the private key field (this was the private key we generated earlier). Then click ADD SSH Key to add the key. Perhaps if you have any issues adding your private key to CircleCI, you can contact or check CircelCI support.

    Upon adding the key, you'll see the fingerprint of your newly added key. As fingerprints are safer than exposing private keys directly, CircleCI uses fingerprints to identify private keys in its scripts.

    After that, you need to ensure that the “pass secrets to builds from forked pull requests” options on our Advanced Setting Page are set to Off. You can locate the Advanced Setting link on the side menu of our project setting page.

    To complete our security checks, please ensure that you delete the private key file from the project root folder.

    Next, we need to submit our public key to our GitHub account. You can do this by going to the Settings tab of your GitHub project repository. On the side menu, click Deploy Keys. Then click Add deploy key on the Deploy Keys page, and the form below will appear:

    Entry form to deploy keys

    Input a relatable name in the title field, e.g., “CircleCI Deployment Key”. In the Key field, paste the content in your public key file “deploy_key.pub” and check the Allow write access checkbox. Then click Add key.

    Your key will be displayed in the list of deployed keys. To finish up, we need to add the fingerprint of our private key from CircleCI to our configuration file. Our deploy steps will look like this below:

    - run:
              name: Install and configure dependencies
              command: |
                npm install gh-pages --save-dev
                git config user.email "youremail@gmail.com"
                git config user.name "daacode"
          - add_ssh_keys:
              fingerprints:
                - "c2:08:17:00:f0:90:43:32:bc:5c:1f:28:6d:01:25:c2"
          - 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
    

    Make sure you replace the fingerprint used here with your own.

    Here is the complete configuration file config.yml:

    version: 2.1
    jobs:
      build:
        working_directory: ~/repo
        docker:
          - image: cimg/node:12.16.0
        steps:
          - checkout
          - run:
              name: update-npm
              command: "sudo npm install -g npm@6"
          - 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 "yourgmail@gmail.com"
                git config user.name "daacode"
          - add_ssh_keys:
              fingerprints:
                - "c2:08:17:00:f0:90:43:32:bc:5c:1f:28:6d:01:25:c2"
          - 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
    

    Finally, commit your changes and push to the main branch once more to trigger the deployment. As soon as you push, it will initiate a new build process, which will be successful this time. Then click the deployment tab and check the test section. You will discover that all our tests are running successfully:

    Confirmation screen that all tests are running successfully

    If you want to verify that our application is deployed, visit your GitHub page URL "https://[your_github_username.github.io/nuxt-cicd]", and view the deployed nuxt application. You will see the homepage of the nuxt application.

    You can find the complete repo for this tutorial here on GitHub.

    Conclusion

    Utilizing the CICD pipeline can help reduce manual errors and provide feedback to developers, allowing for faster product iteration. Developers building server-side rendered applications with Nuxt.js should leverage these modern processes to deploy their applications. We demonstrated how to leverage CICD to successfully deploy a Nuxt.js application to GitHub Pages. CircleCI offers a comprehensive list of configuration options, so we could tailor our configuration to suit our specific needs and deploy it to other hosting providers.

    Data-rich bug reports loved by everyone

    Get visual proof, steps to reproduce and technical logs with one click

    Make bug reporting 50% faster and 100% less painful

    Rating LogosStars
    4.6
    |
    Category leader

    Liked the article? Spread the word

    Put your knowledge to practice

    Try Bird on your next bug - you’ll love it

    “Game changer”

    Julie, Head of QA

    star-ratingstar-ratingstar-ratingstar-ratingstar-rating

    Overall rating: 4.7/5

    Try Bird later, from your desktop