DEV Community

Cover image for Setting Up Full CI/CD Pipeline for React Websites Using Jenkins and NginX
Web Decode
Web Decode

Posted on

Setting Up Full CI/CD Pipeline for React Websites Using Jenkins and NginX

After you developed your React website you want to set up a CI/CD pipeline that automates the delivery of the application to the end-users? This article briefly shows you how to do it!

Step 0 - Full Jenkinsfile Configuration

pipeline {
    agent any
    options {
        timeout(time: 1, unit: 'HOURS')
    }
    stages {
        stage('Checkout Code') {
            steps {
                checkout scm
            }
        }
        stage('Install npm and node js') {
            steps {
                script {
                    def npmVersion = sh(script: 'npm -v', returnStatus: true)
                    if (npmVersion != 0) {
                        echo 'Installing npm and node js'
                        sh '''
                            sudo apt update
                            curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
                            sudo apt install nodejs -y
                            sudo apt install build-essential
                        '''
                    } else {
                        echo 'npm and node js already installed'
                    }
                }
            }
        }
        stage('Install Dependencies') {
            steps {
                sh 'npm i'
            }
        }
        stage('Build') {
            steps {
                sh 'npm run build'
            }
        }
        stage ('Move to Nginx') {
            steps {
                sh 'sudo mkdir -p /var/www/html/octopus'
                sh 'sudo rm -rf /var/www/html/octopus/dist'  // Safely remove the directory if exists
                sh 'sudo mv dist /var/www/html/octopus'
            }
        }
        stage ('Configure Nginx') {
            steps {
                script {
                    def configFile = '''
                       server {
                           listen 80;
                           location / {
                               return 301 https://85.120.206.53$request_uri;
                           }
                       }
                       server {
                           listen 443 ssl http2;
                           include /etc/nginx/ssl/ssl_all_sites.conf;
                           include /etc/nginx/ssl/ssl_codelighthouse.conf;

                           root /var/www/html/octopus/dist;
                           index index.html;

                           location / {
                               try_files $uri /index.html;
                           }
                       }
                    '''
                    writeFile file: '/etc/nginx/sites-available/octopus.conf', text: configFile
                    def file = new File('/etc/nginx/sites-enabled/octopus.conf')
                    if (!file.exists()) {
                        sh 'sudo ln -s /etc/nginx/sites-available/octopus.conf /etc/nginx/sites-enabled'
                    }
                }
            }
        }
        stage ('Restart Nginx') {
            steps {
                sh 'sudo systemctl reload nginx'
            }
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

Step 1 - Add the Jenkinsfile

You are going to want to create a Jenkinsfile inside your website folder. This will serve as the code that Jenkins will use to create your pipeline.

Step 2 - Set up the Jenkinsfile code

Open up a pipeline that can run on any agent (or any specific one if you have) and has a timeout of an hour to avoid infinite running.

pipeline {
    agent any
    options {
        timeout(time: 1, unit: 'HOURS')
    }
Enter fullscreen mode Exit fullscreen mode

Then define your stages as such:

Stage - Checkout code

First, checkout the code from the SCM e.g GitHub:

stages {
        stage('Checkout Code') {
            steps {
                checkout scm
            }
        }
Enter fullscreen mode Exit fullscreen mode

Stage - Install npm and node

Then make sure you have npm and node installed:

stage('Install npm and node js') {
            steps {
                script {
                    def npmVersion = sh(script: 'npm -v', returnStatus: true)
                    if (npmVersion != 0) {
                        echo 'Installing npm and node js'
                        sh '''
                            sudo apt update
                            curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
                            sudo apt install nodejs -y
                            sudo apt install build-essential
                        '''
                    } else {
                        echo 'npm and node js already installed'
                    }
                }
            }
        }
Enter fullscreen mode Exit fullscreen mode

Let's explain each line:

def npmVersion = sh(script: 'npm -v', returnStatus: true)
Enter fullscreen mode Exit fullscreen mode

defines a variable called npmVersion that will either hold the version of the already installed npm package or 0 if npm is missing.

sh '''
  sudo apt update
  curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
  sudo apt install nodejs -y
  sudo apt install build-essential
'''
Enter fullscreen mode Exit fullscreen mode

will first update apt, add the latest version of node because the default version that apt will fetch is a very old one; install the lts version of node that also comes with npm; installs build-essentials that is required by npm to compile source scripts.

Stage - Install Dependencies

stage('Install Dependencies') {
            steps {
                sh 'npm i'
            }
        }
Enter fullscreen mode Exit fullscreen mode

Stage - Build React app

stage('Build') {
            steps {
                sh 'npm run build'
            }
        }
Enter fullscreen mode Exit fullscreen mode

Stage - Move files to Nginx directory

stage ('Move to Nginx') {
            steps {
                sh 'sudo mkdir -p /var/www/html/octopus'
                sh 'sudo rm -rf /var/www/html/octopus/dist'  // Safely remove the directory if exists
                sh 'sudo mv dist /var/www/html/octopus'
            }
        }
Enter fullscreen mode Exit fullscreen mode

Let's explain each line:

sh 'sudo mkdir -p /var/www/html/octopus'
Enter fullscreen mode Exit fullscreen mode

creates a folder that will hold the built web app inside the NginX serving folder.

sh 'sudo rm -rf /var/www/html/octopus/dist'
Enter fullscreen mode Exit fullscreen mode

removes the folder dist if it already exists e.g from previos builds of the pipeline.

sh 'sudo mv dist /var/www/html/octopus'
Enter fullscreen mode Exit fullscreen mode

moves the built folder dist inside the previously created folder.

Stage - Configure Nginx

stage ('Configure Nginx') {
            steps {
                script {
                    def configFile = '''
                       server {
                           listen 80;

                           root /var/www/html/octopus/dist;
                           index index.html;

                           location / {
                               try_files $uri /index.html;
                           }
                       }
                    '''
                    writeFile file: '/etc/nginx/sites-available/octopus.conf', text: configFile
                    def file = new File('/etc/nginx/sites-enabled/octopus.conf')
                    if (!file.exists()) {
                        sh 'sudo ln -s /etc/nginx/sites-available/octopus.conf /etc/nginx/sites-enabled'
                    }
                }
            }
        }
Enter fullscreen mode Exit fullscreen mode

Let's explain the above code:

listen 80;
Enter fullscreen mode Exit fullscreen mode

listen on the unsecured http port: 80.

root /var/www/html/octopus/dist;
Enter fullscreen mode Exit fullscreen mode

sets the root for easier access to files. All further references will be made with regards to this root when it comes to paths.

index index.html;
Enter fullscreen mode Exit fullscreen mode

tells NginX to always return index.html when a folder is requested with no file e.g http:/site.com/ or http:/site.com/folder/.

location / {
       try_files $uri /index.html;
}
Enter fullscreen mode Exit fullscreen mode

tells NginX to try to serve the file requested i.e $uri that can look like http://site.com/file.php for example and if it can't find it, return the index.html file. This is imperative for React builds as the router works by taking in requests for the index.html file. Failing to redirect requests like http://site.com/app to the index.html file will result in the routing not working.

Stage - Restart Nginx

stage ('Restart Nginx') {
            steps {
                sh 'sudo systemctl reload nginx'
            }
        }
Enter fullscreen mode Exit fullscreen mode

Note: Always use reload instead of restart. If the configuration file is flawed and throws an error restart will leave NginX down, whereas reload will throw the error but will not load the flawed configuration file so NginX will continue to work with the old configuration.

Step 3 - Push Jenkinsfile to GitHub

You can now go ahead and push the configuration file to the GitHub repository.

Step 4 - Allow Jenkins to Run sudo Commands

We used many sudo commands in the pipeline code, but we have to grant Jenkins the rights to act as a super user with no password input requirements.

Step 4.1 - Jenkins User

Make sure the server running the Jenkins controller has a Jenkins user created:

id jenkins
Enter fullscreen mode Exit fullscreen mode

If the user does not exist, create it:

sudo useradd -m jenkins
Enter fullscreen mode Exit fullscreen mode

Flags used:

  • -m creates a home directory for the user

Step 4.2 - Make Jenkins a Super User

Add the Jenkins user to the super users group (sudo)

sudo usermod -aG sudo jenkins
Enter fullscreen mode Exit fullscreen mode

Flags used:

  • -a append a new group to the existing ones
  • -G specify a group to add the user to

Step 4.3 - Allow Jenkins Super User to not Utilize Passwords

Open the visudo file:

sudo visudo
Enter fullscreen mode Exit fullscreen mode

and add Jenkins at the end of the file:

jenkins ALL=(ALL) NOPASSWD: ALL
Enter fullscreen mode Exit fullscreen mode

Step 5 - Create a Jenkins Multi-Branch Pipeline

Inside Jenkins you have to create a new Multi-Branch pipeline that will scan all the branches of your repository and create pipelines for them based on the found Jenkinsfile.

This can be done by following this route inside Jenkins:
Dashboard -> New Item -> Enter a name -> Multibranch Pipeline
Then go ahead and set up the GitHub repo:
Repository HTTPS URL

Step 6 - Build the Pipeline

Build the pipeline by clicking on one of your branches and then Build Now

Step 7 - Set up GitHub Webhooks

If your pipeline has been successfully created, you just have to tell GitHub to let Jenkins know of any push that happens to the repository. This can be done via webhooks:
Inside GitHub repo:
Settings -> Webhooks -> Add Webhook
The Payload URL is http://<your_server_ip>:8080/github-webhook/

Conclusions

This article has briefly showed you how to set up a full CI/CD pipeline for delivering your React app using NginX and Jenkins. Everytime you push changes to your repository, you can expect to see them live in a few minutes.

Note:This is just a basic tutorial, many more security and performance steps must be completed in order to have an industrial-grade pipeline.

Sentry blog image

How I fixed 20 seconds of lag for every user in just 20 minutes.

Our AI agent was running 10-20 seconds slower than it should, impacting both our own developers and our early adopters. See how I used Sentry Profiling to fix it in record time.

Read more

Top comments (0)

Billboard image

Create up to 10 Postgres Databases on Neon's free plan.

If you're starting a new project, Neon has got your databases covered. No credit cards. No trials. No getting in your way.

Try Neon for Free β†’

πŸ‘‹ Kindness is contagious

Please leave a ❀️ or a friendly comment on this post if you found it helpful!

Okay