CI/CD Deployment pipeline for a React application using Next.js, Cypress, CircleCi and Netlify in 5 minutes

Terchilă Marian
6 min readJan 15, 2022

This guide will cover the creation of a deployment pipeline for an existing react application, in our case, we will be doing this for the Cutieverse, one of my personal projects. For this example, I will use next.js which is a react framework meant for production environments. It features hybrid static & server rendering, TypeScript support, smart bundling, route pre-fetching, and more.

The overall idea of what we are doing is quite simple. First, we want to build the application and ensure that there are no errors whilst building it. Secondly, we want to run the test suite which in this scenario we will be using cypress for end to end testing. Ultimately, once the build and the tests pass we want to trigger the build deployment through a webhook.

Prerequisites:

  • An existing react application published on Github (the repo can be private)
  • CircleCi (I’ve chosen this platform due to the ease of use of the service, the same outcome can be accomplished using any different CI/CD platform)
  • Cypress (for this example we will use cypress, but feel free to replace it with jest or any other library you want or have a mix in between)
  • Netlify (any other web host works as long as it futures webhooks that can trigger deployments)
  • Yarn

Let’s get started

First step

To begin with, create a CircleCi account in case you don’t already have one and link your existing GitHub repository to it.

The next thing you want to do is to create a folder in the root folder of your application named .circleci and a file within it named config.yml

This file will hold the configuration file of our deployment pipeline.

For more info about this step feel free to read the CircleCi docs.

Second step

To continue you need to integrate a test suite in your existing react application. For this example, we will continue with Cypress which is a library meant to write end to end tests for web applications.

Because the cypress requires a running web server to test against we will use the node librarystart-server-and-testto handle that.

  1. Install the node packages
yarn add -D cypress start-server-and-test

2. Start cypress for the first time

yarn run cypress open

This will add a bunch of folders to the root of your app:

  • cypress/fixtures → mock server response
  • cypress/integration → these our UI tests
  • cypress/plugins → cypress plugins
  • cypress/supports → reusable functions to use in our tests

By default, this will generate a whole bunch of test examples to

  • cypress/integration/examples

Now you would probably like to start writing your tests. For the sake of testing our cypress implementation worked, I’ve got rid of all the examples they provide by default and wrote a simple test.

/// <reference types="cypress" />// Welcome to Cypress!
// To learn more about how Cypress works and
// what makes it such an awesome testing tool,
// please read our getting started guide:
// <https://on.cypress.io/introduction-to-cypress>
describe('Testing the hero section of the home page', () => {
beforeEach(() => {
// Cypress starts out with a blank slate for each test
// so we must tell it to visit our website with the `cy.visit()` command.
// Since we want to visit the same URL at the start of all our tests,
// we include it in our beforeEach function so that it runs before each test
// we also setup the viewport
cy.viewport(1920, 1080)
cy.visit('/')
})
it('It works!', () => {
expect(true).to.equal(true)
})
})

To run the test suite use the following command:

yarn test:e2e

3. Update the package.json file

"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"export": "next export",
"prod": "next build && next export && serve -s out",
"test:cy": "cypress open",
"test:e2e": "start-server-and-test dev 3000 'cypress run --record --key <YOURKEY>'"
},
  • test:cy will open the cypress GUI
  • test:e2e will start the server in dev mode on the 3000 port then run cypress

As you can see there are a few flags attached to this --record --key <YOURKEY>'

The record and key flags are optional but I chose to add them in order to take full advantage of the cypress functionality where I can come later on and see how every single test performed.

This is how it looks from their dashboard.

To do this you want to link your project to the cypress dashboard, get your project key and update the <YOURKEY> section.

Besides that, you’ll also have to update the cypress.json file with the projectId you get from the dashboard. It should look something similar to this at the end.

{
"baseUrl": "<http://localhost:3000>",
"projectId": "xxxxxx"
}

References: Next.js Testing Guide

Third step

Now that we have cypress up and running we want to set up the pipeline for our project.

You can simply copy and paste the code below or modify the pipeline according to your needs.

version: 2.1
jobs:
build:
working_directory: ~/app
docker:
- image: circleci/node:14.18.2
steps:
- checkout
- run:
name: Install Yarn
command: curl -o- -L <https://yarnpkg.com/install.sh> | bash
- restore_cache:
key: dependency-cache-{{ checksum "package.json" }}
- run:
name: Install Dependencies
command: yarn
- save_cache:
key: dependency-cache-{{ checksum "package.json" }}
paths:
- ./node_modules
- run:
name: Build project
command: yarn build
- save_cache:
key: dependency-cache-{{ checksum "package.json" }}
paths:
- ./node_modules
- run:
name: Export project
command: yarn export
test:
docker:
- image: cypress/base:14.17.0
environment:
## this enables colors in the output
TERM: xterm
working_directory: ~/app
steps:
- checkout
- restore_cache:
keys:
- v1-deps-{{ .Branch }}-{{ checksum "package.json" }}
- v1-deps-{{ .Branch }}
- v1-deps
- run:
name: Install Yarn
command: curl -o- -L <https://yarnpkg.com/install.sh> | bash
- run:
name: Install Dependencies
# yarn installs the app dependencies
command: yarn && yarn global add serve
- save_cache:
key: v1-deps-{{ .Branch }}-{{ checksum "package.json" }}
paths:
- ~/.cache ## cache both yarn and Cypress!
- run: yarn test:e2e
deploy:
working_directory: ~/app
docker:
- image: curlimages/curl:7.81.0
# use the webhook from netlify that was added as a env var to the circleci project to trigger the build
steps:
- run:
name: Deploy
command: curl -X POST -d {} $DEPLOY_WEBHOOK
workflows:
version: 2
build_and_test:
jobs:
- build
- test
# deploy only if the build and the test steps go through
- deploy:
requires:
- build
- test

The pipeline above consists of three stages.

  1. Build the production application
  2. Run the test suite using cypress and record the results
  3. Deploy our project to our web host (netlify)

As you can see here, I use an environment variable named DEPLOY_WEBHOOK to make the curl request that triggers the webhook. Environment variables were used in this scenario to prevent security bridges or individuals from spamming the deployment webhook.

steps:
- run:
name: Deploy
command: curl -X POST -d {} $DEPLOY_WEBHOOK

To do this you first need to grab your deployment webhook or generate one from netlify or whichever provider you have.

You can easily create one on netlify from the build & deploy page.

Once you have the address of your webhook switch over to the CircleCi dashboard and add it as an environment variable.

Voila, we’re all set and done. Hope you enjoyed this guide and got a rough understanding of what it takes to build a deployment pipeline. From this point on everything pushed to the master branch will automatically be deployed if the build and tests pass.

--

--