Documentation Index Fetch the complete documentation index at: https://devtools.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
Introduction to Frontend Deployment and CI/CD
Deploying frontend applications and implementing effective CI/CD (Continuous Integration/Continuous Delivery) pipelines are crucial aspects of modern frontend development. This guide covers best practices for deploying frontend applications to various environments and setting up automated workflows to ensure reliable, consistent deployments.
Deployment Strategies Learn about different deployment approaches for frontend applications.
CI/CD Pipelines Implement automated workflows for testing, building, and deploying your
applications.
Environment Management Manage different environments (development, staging, production) effectively.
Performance Monitoring Monitor your deployed applications for performance and errors.
Frontend Deployment Fundamentals
Understanding the Frontend Deployment Process
Deploying a frontend application typically involves these steps:
Building the application : Compiling, bundling, and optimizing the code for production
Asset optimization : Minifying code, optimizing images, and generating asset manifests
Uploading to hosting : Transferring the built files to a hosting provider
Configuring the environment : Setting up environment variables, CDN, and caching
Validating the deployment : Testing the deployed application
Static vs. Server-Rendered Deployments
Static Site Deployment
Static sites consist of HTML, CSS, JavaScript, and other assets that don’t require server-side rendering at request time.
Advantages:
Simpler deployment process
Can be served from CDNs for better performance
Lower hosting costs
Better security due to reduced attack surface
Common hosting options:
Netlify
Vercel
GitHub Pages
AWS S3 + CloudFront
Firebase Hosting
Cloudflare Pages
Server-Rendered Deployment
Server-rendered applications generate HTML on the server for each request.
Advantages:
Better SEO potential
Faster initial page load
Works better for dynamic content
Common hosting options:
Vercel
Netlify
Heroku
AWS Elastic Beanstalk
Google App Engine
DigitalOcean App Platform
Deployment Strategies
Basic Deployment
The simplest deployment strategy involves building your application and uploading it to a hosting provider.
# Build the application
npm run build
# Deploy to hosting provider
npx netlify deploy --prod
Blue-Green Deployment
Blue-green deployment involves maintaining two identical production environments (blue and green). At any time, only one environment is live and serving production traffic.
Deploy new version to the inactive environment (e.g., green)
Test the new deployment
Switch traffic from active (blue) to inactive (green)
The previously active environment (blue) becomes inactive
Benefits:
Zero downtime deployments
Easy rollback by switching back to the previous environment
Reduced risk as the new version is fully tested before receiving traffic
Canary Deployment
Canary deployment involves gradually routing a small percentage of traffic to the new version.
Deploy the new version alongside the current version
Route a small percentage (e.g., 5%) of traffic to the new version
Monitor for errors and performance issues
Gradually increase traffic to the new version
Once confident, route 100% of traffic to the new version
Benefits:
Reduced risk by limiting exposure to the new version
Ability to test with real users and traffic
Early detection of issues that might not appear in testing environments
Feature Flags
Feature flags allow you to toggle features on or off without deploying new code.
// Example of a feature flag in code
if ( featureFlags . isEnabled ( 'new-ui' )) {
// Show new UI
renderNewUI ();
} else {
// Show old UI
renderOldUI ();
}
Benefits:
Decouple deployment from feature release
Test features with specific user segments
Quick rollback by disabling problematic features
A/B testing capabilities
Static Site Hosting
Netlify
Netlify is a popular platform for deploying static sites and serverless functions.
Setup:
Create a netlify.toml configuration file:
[ build ]
command = "npm run build"
publish = "dist"
[[ redirects ]]
from = "/*"
to = "/index.html"
status = 200
Deploy using the Netlify CLI:
npx netlify deploy --prod
Key features:
Continuous deployment from Git
Branch deploys and deploy previews
Serverless functions
Form handling
Split testing
Vercel
Vercel is a cloud platform for static sites and serverless functions, optimized for frontend frameworks.
Setup:
Create a vercel.json configuration file:
{
"version" : 2 ,
"builds" : [
{
"src" : "package.json" ,
"use" : "@vercel/static-build" ,
"config" : { "distDir" : "dist" }
}
],
"routes" : [
{ "handle" : "filesystem" },
{ "src" : "/.*" , "dest" : "/index.html" }
]
}
Deploy using the Vercel CLI:
Key features:
Optimized for Next.js, but supports many frameworks
Preview deployments for every Git branch
Serverless functions
Edge network for global distribution
Built-in analytics
AWS S3 + CloudFront
AWS S3 combined with CloudFront provides a scalable, cost-effective solution for hosting static sites.
Setup:
Create an S3 bucket and configure it for static website hosting
Set up CloudFront distribution pointing to the S3 bucket
Deploy using the AWS CLI:
# Build the application
npm run build
# Sync files to S3
aws s3 sync dist/ s3://your-bucket-name/ --delete
# Invalidate CloudFront cache
aws cloudfront create-invalidation --distribution-id YOUR_DISTRIBUTION_ID --paths "/*"
Key features:
Highly scalable and reliable
Low cost for high traffic sites
Global content delivery via CloudFront
Fine-grained access control
Server-Rendered Hosting
Heroku
Heroku is a platform as a service (PaaS) that enables developers to build, run, and operate applications entirely in the cloud.
Setup:
Create a Procfile in your project root:
Deploy using the Heroku CLI:
Key features:
Simple deployment workflow
Automatic scaling
Add-ons for databases and other services
Built-in logging and monitoring
DigitalOcean App Platform is a PaaS solution that simplifies deploying and scaling applications.
Setup:
Create a app.yaml configuration file:
name : my-frontend-app
services :
- name : web
github :
repo : username/repo-name
branch : main
build_command : npm run build
run_command : npm start
http_port : 8080
instance_count : 1
instance_size_slug : basic-xs
routes :
- path : /
Key features:
Simple pricing model
Global CDN
Managed databases
Automatic HTTPS
Continuous Integration and Continuous Delivery (CI/CD)
CI/CD Fundamentals
CI/CD is a method to frequently deliver apps to customers by introducing automation into the development stages.
Continuous Integration (CI) : Automatically building and testing code changes
Continuous Delivery (CD) : Automatically deploying code changes to staging or production environments
Setting Up CI/CD Pipelines
GitHub Actions
GitHub Actions allows you to automate your workflow directly from your GitHub repository.
Example workflow for a React application:
# .github/workflows/deploy.yml
name : Deploy
on :
push :
branches : [ main ]
jobs :
build-and-deploy :
runs-on : ubuntu-latest
steps :
- name : Checkout
uses : actions/checkout@v3
- name : Setup Node.js
uses : actions/setup-node@v3
with :
node-version : '16'
cache : 'npm'
- name : Install dependencies
run : npm ci
- name : Run tests
run : npm test
- name : Build
run : npm run build
env :
REACT_APP_API_URL : ${{ secrets.API_URL }}
- name : Deploy to Netlify
uses : netlify/actions/cli@master
with :
args : deploy --dir=build --prod
env :
NETLIFY_AUTH_TOKEN : ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID : ${{ secrets.NETLIFY_SITE_ID }}
GitLab CI/CD
GitLab CI/CD is a tool built into GitLab for software development through continuous integration and delivery.
Example .gitlab-ci.yml for a Vue.js application:
stages :
- test
- build
- deploy
cache :
paths :
- node_modules/
test :
stage : test
image : node:16
script :
- npm ci
- npm run lint
- npm run test:unit
build :
stage : build
image : node:16
script :
- npm ci
- npm run build
artifacts :
paths :
- dist/
deploy_staging :
stage : deploy
image : alpine:latest
script :
- apk add --no-cache curl
- curl -X POST -d '{"deploy_to":"staging"}' -H "Authorization : Bearer $DEPLOY_TOKEN" $DEPLOY_HOOK_URL
environment :
name : staging
url : https://staging.example.com
only :
- develop
deploy_production :
stage : deploy
image : alpine:latest
script :
- apk add --no-cache curl
- curl -X POST -d '{"deploy_to":"production"}' -H "Authorization : Bearer $DEPLOY_TOKEN" $DEPLOY_HOOK_URL
environment :
name : production
url : https://example.com
only :
- main
when : manual
CircleCI
CircleCI is a cloud-based CI/CD service that automates the build, test, and deployment process.
Example .circleci/config.yml for an Angular application:
version : 2.1
jobs :
build-and-test :
docker :
- image : cimg/node:16.13
steps :
- checkout
- restore_cache :
keys :
- v1-dependencies-{{ checksum "package-lock.json" }}
- v1-dependencies-
- run : npm ci
- save_cache :
paths :
- node_modules
key : v1-dependencies-{{ checksum "package-lock.json" }}
- run : npm run lint
- run : npm run test -- --no-watch --no-progress --browsers=ChromeHeadlessCI
- run : npm run build -- --configuration production
- persist_to_workspace :
root : .
paths :
- dist
deploy :
docker :
- image : cimg/node:16.13
steps :
- checkout
- attach_workspace :
at : .
- run :
name : Install Firebase Tools
command : npm install -g firebase-tools
- run :
name : Deploy to Firebase
command : firebase deploy --token "$FIREBASE_TOKEN" --only hosting
workflows :
version : 2
build-test-deploy :
jobs :
- build-and-test
- deploy :
requires :
- build-and-test
filters :
branches :
only : main
Environment-Specific Configurations
Managing different configurations for development, staging, and production environments is crucial for a robust deployment pipeline.
Environment Variables
Environment variables are a common way to manage environment-specific configurations.
React with Create React App:
Create environment-specific files:
.env.development
.env.test
.env.production
# .env.production
REACT_APP_API_URL=https://api.example.com
REACT_APP_FEATURE_FLAG_NEW_UI=true
Vue.js:
Create environment-specific files:
.env.development
.env.test
.env.production
# .env.production
VUE_APP_API_URL=https://api.example.com
VUE_APP_FEATURE_FLAG_NEW_UI=true
Angular:
Create environment-specific files in the src/environments directory:
environment.ts
environment.prod.ts
// environment.prod.ts
export const environment = {
production: true ,
apiUrl: 'https://api.example.com' ,
featureFlags: {
newUi: true ,
},
};
Runtime Configuration
Sometimes you need to load configuration at runtime rather than build time.
Example using a config.json file:
// Load configuration at runtime
async function loadConfig () {
try {
const response = await fetch ( '/config.json' );
return await response . json ();
} catch ( error ) {
console . error ( 'Failed to load configuration:' , error );
return {};
}
}
// Usage
loadConfig (). then (( config ) => {
console . log ( 'API URL:' , config . apiUrl );
initializeApp ( config );
});
Optimizing the Deployment Process
Build Optimization
Reducing Bundle Size
// webpack.config.js for a React application
const TerserPlugin = require ( 'terser-webpack-plugin' );
const CompressionPlugin = require ( 'compression-webpack-plugin' );
module . exports = {
// ...
optimization: {
minimizer: [ new TerserPlugin ()],
splitChunks: {
chunks: 'all' ,
maxInitialRequests: Infinity ,
minSize: 0 ,
cacheGroups: {
vendor: {
test: / [ \\ / ] node_modules [ \\ / ] / ,
name ( module ) {
// Get the name of the npm package
const packageName = module . context . match (
/ [ \\ / ] node_modules [ \\ / ] ( . +? )(?: [ \\ / ] |$ ) /
)[ 1 ];
return `npm. ${ packageName . replace ( '@' , '' ) } ` ;
},
},
},
},
},
plugins: [
// ...
new CompressionPlugin ({
algorithm: 'gzip' ,
test: / \. ( js | css | html | svg ) $ / ,
threshold: 10240 ,
minRatio: 0.8 ,
}),
],
};
Tree Shaking
Tree shaking is a term commonly used in the JavaScript context for dead-code elimination.
// Import only what you need
// Good - only imports Button
import { Button } from 'ui-library' ;
// Bad - imports the entire library
import UILibrary from 'ui-library' ;
const { Button } = UILibrary ;
Code Splitting
Code splitting is a technique to split your code into various bundles which can then be loaded on demand.
React with React.lazy and Suspense:
import React , { Suspense , lazy } from 'react' ;
import { BrowserRouter as Router , Routes , Route } from 'react-router-dom' ;
const Home = lazy (() => import ( './routes/Home' ));
const About = lazy (() => import ( './routes/About' ));
const Contact = lazy (() => import ( './routes/Contact' ));
function App () {
return (
< Router >
< Suspense fallback = { < div > Loading... </ div > } >
< Routes >
< Route path = "/" element = { < Home /> } />
< Route path = "/about" element = { < About /> } />
< Route path = "/contact" element = { < Contact /> } />
</ Routes >
</ Suspense >
</ Router >
);
}
Vue.js with dynamic imports:
// router.js
const routes = [
{
path: '/' ,
name: 'Home' ,
component : () => import ( './views/Home.vue' ),
},
{
path: '/about' ,
name: 'About' ,
component : () => import ( './views/About.vue' ),
},
{
path: '/contact' ,
name: 'Contact' ,
component : () => import ( './views/Contact.vue' ),
},
];
Caching Strategies
Proper cache headers ensure that browsers cache assets appropriately.
Example Netlify _headers file:
# _headers
/assets/*
Cache-Control: public, max-age=31536000, immutable
/*.html
Cache-Control: public, max-age=0, must-revalidate
/*.js
Cache-Control: public, max-age=31536000, immutable
/*.css
Cache-Control: public, max-age=31536000, immutable
Cache Busting
Cache busting is a technique that prevents browsers from using cached versions of assets when they’ve been updated.
Webpack content hash:
// webpack.config.js
module . exports = {
// ...
output: {
filename: '[name].[contenthash].js' ,
path: path . resolve ( __dirname , 'dist' ),
},
};
Automated Testing in CI/CD
Unit Tests
# GitHub Actions workflow with Jest
name : Test
on : [ push , pull_request ]
jobs :
test :
runs-on : ubuntu-latest
steps :
- uses : actions/checkout@v3
- uses : actions/setup-node@v3
with :
node-version : '16'
cache : 'npm'
- run : npm ci
- run : npm test
End-to-End Tests
# GitHub Actions workflow with Cypress
name : E2E Tests
on : [ push , pull_request ]
jobs :
cypress-run :
runs-on : ubuntu-latest
steps :
- name : Checkout
uses : actions/checkout@v3
- name : Cypress run
uses : cypress-io/github-action@v5
with :
build : npm run build
start : npm start
wait-on : 'http://localhost:3000'
Monitoring and Observability
Error Tracking
Sentry Integration
// index.js for a React application
import React from 'react' ;
import ReactDOM from 'react-dom' ;
import * as Sentry from '@sentry/react' ;
import { BrowserTracing } from '@sentry/tracing' ;
import App from './App' ;
Sentry . init ({
dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' ,
integrations: [ new BrowserTracing ()],
tracesSampleRate: 1.0 ,
});
ReactDOM . render (
< React.StrictMode >
< App />
</ React.StrictMode > ,
document . getElementById ( 'root' )
);
Web Vitals
// reportWebVitals.js
import { getCLS , getFID , getLCP , getFCP , getTTFB } from 'web-vitals' ;
const reportWebVitals = ( onPerfEntry ) => {
if ( onPerfEntry && onPerfEntry instanceof Function ) {
getCLS ( onPerfEntry );
getFID ( onPerfEntry );
getLCP ( onPerfEntry );
getFCP ( onPerfEntry );
getTTFB ( onPerfEntry );
}
};
export default reportWebVitals ;
// index.js
import React from 'react' ;
import ReactDOM from 'react-dom' ;
import App from './App' ;
import reportWebVitals from './reportWebVitals' ;
ReactDOM . render (
< React.StrictMode >
< App />
</ React.StrictMode > ,
document . getElementById ( 'root' )
);
// Send web vitals to analytics
reportWebVitals (({ name , delta , id }) => {
// Send to Google Analytics
gtag ( 'event' , name , {
event_category: 'Web Vitals' ,
event_label: id ,
value: Math . round ( name === 'CLS' ? delta * 1000 : delta ),
non_interaction: true ,
});
});
Logging
Centralized Logging
// logger.js
class Logger {
constructor () {
this . logs = [];
this . maxLogs = 100 ;
}
log ( level , message , data = {}) {
const logEntry = {
timestamp: new Date (). toISOString (),
level ,
message ,
data ,
};
this . logs . push ( logEntry );
// Keep logs under the maximum size
if ( this . logs . length > this . maxLogs ) {
this . logs . shift ();
}
// Log to console in development
if ( process . env . NODE_ENV === 'development' ) {
console [ level ]( message , data );
}
// Send to centralized logging service in production
if ( process . env . NODE_ENV === 'production' ) {
this . sendToLoggingService ( logEntry );
}
}
async sendToLoggingService ( logEntry ) {
try {
await fetch ( 'https://logging.example.com/api/logs' , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/json' ,
},
body: JSON . stringify ( logEntry ),
});
} catch ( error ) {
// Fallback to console in case of failure
console . error ( 'Failed to send log to logging service:' , error );
}
}
info ( message , data ) {
this . log ( 'info' , message , data );
}
warn ( message , data ) {
this . log ( 'warn' , message , data );
}
error ( message , data ) {
this . log ( 'error' , message , data );
}
debug ( message , data ) {
this . log ( 'debug' , message , data );
}
}
export const logger = new Logger ();
Rollback Strategies
Manual Rollback
# Example rollback command for Netlify
npx netlify deploy --prod --dir=build --site-id= $SITE_ID --auth= $AUTH_TOKEN --message= "Rolling back to previous version"
# Example rollback command for Vercel
vercel rollback --prod
Automated Rollback
# GitHub Actions workflow with automated rollback
name : Deploy with Rollback
on :
push :
branches : [ main ]
jobs :
deploy :
runs-on : ubuntu-latest
steps :
- name : Checkout
uses : actions/checkout@v3
- name : Setup Node.js
uses : actions/setup-node@v3
with :
node-version : '16'
cache : 'npm'
- name : Install dependencies
run : npm ci
- name : Build
run : npm run build
- name : Deploy
id : deploy
run : |
DEPLOY_OUTPUT=$(npx netlify deploy --prod --json)
DEPLOY_ID=$(echo $DEPLOY_OUTPUT | jq -r '.deployId')
echo "::set-output name=deploy_id::$DEPLOY_ID"
env :
NETLIFY_AUTH_TOKEN : ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID : ${{ secrets.NETLIFY_SITE_ID }}
- name : Test deployment
id : test_deployment
run : |
# Run tests against the deployed site
RESPONSE_CODE=$(curl -s -o /dev/null -w "%{http_code}" https://example.com)
if [[ "$RESPONSE_CODE" -ne 200 ]]; then
echo "Deployment failed with response code $RESPONSE_CODE"
exit 1
fi
- name : Rollback on failure
if : failure() && steps.deploy.outputs.deploy_id != ''
run : |
npx netlify deploy --prod-id=${{ steps.deploy.outputs.deploy_id }} --restore
env :
NETLIFY_AUTH_TOKEN : ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID : ${{ secrets.NETLIFY_SITE_ID }}
Conclusion
Implementing effective deployment and CI/CD practices is essential for modern frontend development. By following the best practices outlined in this guide, you can create reliable, automated workflows that ensure your applications are deployed consistently and with minimal risk.
Remember these key takeaways:
Choose the right deployment strategy for your application’s needs, whether it’s static hosting, server-rendered, or a hybrid approach.
Automate your workflow with CI/CD pipelines to ensure consistent testing, building, and deployment.
Implement proper environment management to handle different configurations for development, staging, and production.
Optimize your build process to reduce bundle sizes, implement code splitting, and ensure efficient caching.
Monitor your deployed applications for errors, performance issues, and user experience metrics.
Have a rollback strategy in place to quickly recover from problematic deployments.
By implementing these practices, you’ll create a robust deployment process that supports your team’s ability to deliver high-quality frontend applications efficiently and reliably.