CDN Setup for Static Assets - Complete Guide
Published: September 25, 2024 | Reading time: 18 minutes
CDN Overview
Content Delivery Networks (CDN) improve website performance by serving static assets from edge locations:
CDN Benefits
# CDN Benefits
- Faster content delivery
- Reduced server load
- Global content distribution
- Improved user experience
- Bandwidth cost reduction
- DDoS protection
- SSL/TLS termination
Cloudflare CDN Setup
Cloudflare Configuration
Cloudflare Setup Process
# Cloudflare CDN Setup
# 1. Add Domain to Cloudflare
# Visit: https://dash.cloudflare.com/
# Click "Add a Site"
# Enter your domain name
# Choose plan (Free, Pro, Business, Enterprise)
# 2. Update Nameservers
# At your domain registrar, change nameservers to:
ns1.cloudflare.com
ns2.cloudflare.com
# 3. DNS Configuration
# Add A record pointing to your server IP
# Add CNAME records for subdomains
# Enable proxy (orange cloud) for CDN benefits
# 4. Page Rules Configuration
# URL Pattern: example.com/static/*
# Settings:
# - Cache Level: Cache Everything
# - Edge Cache TTL: 1 month
# - Browser Cache TTL: 1 month
# URL Pattern: example.com/images/*
# Settings:
# - Cache Level: Cache Everything
# - Edge Cache TTL: 1 week
# - Browser Cache TTL: 1 week
# 5. Cloudflare Workers (Advanced)
# Custom edge computing
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
const url = new URL(request.url)
// Serve static assets from CDN
if (url.pathname.startsWith('/static/')) {
const response = await fetch(request)
// Add cache headers
const newResponse = new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: {
...response.headers,
'Cache-Control': 'public, max-age=31536000, immutable',
'CDN-Cache-Control': 'max-age=31536000'
}
})
return newResponse
}
return fetch(request)
}
# 6. Cloudflare API Configuration
# Install Cloudflare CLI
npm install -g cloudflare-cli
# Configure API credentials
cfcli config
# Enter API Token or Global API Key
# Create Page Rule via API
curl -X POST "https://api.cloudflare.com/client/v4/zones/ZONE_ID/pagerules" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{
"targets": [
{
"target": "url",
"constraint": {
"operator": "matches",
"value": "example.com/static/*"
}
}
],
"actions": [
{
"id": "cache_level",
"value": "cache_everything"
},
{
"id": "edge_cache_ttl",
"value": 2592000
}
],
"priority": 1,
"status": "active"
}'
AWS CloudFront Setup
CloudFront Configuration
AWS CloudFront Implementation
# AWS CloudFront Setup
# 1. Create S3 Bucket for Static Assets
aws s3 mb s3://your-static-assets-bucket
aws s3 website s3://your-static-assets-bucket --index-document index.html
# Upload static assets
aws s3 sync ./static/ s3://your-static-assets-bucket/static/
aws s3 sync ./images/ s3://your-static-assets-bucket/images/
aws s3 sync ./css/ s3://your-static-assets-bucket/css/
aws s3 sync ./js/ s3://your-static-assets-bucket/js/
# 2. Create CloudFront Distribution
aws cloudfront create-distribution \
--distribution-config '{
"CallerReference": "static-assets-'$(date +%s)'",
"Origins": {
"Quantity": 1,
"Items": [
{
"Id": "S3-static-assets",
"DomainName": "your-static-assets-bucket.s3.amazonaws.com",
"S3OriginConfig": {
"OriginAccessIdentity": ""
}
}
]
},
"DefaultCacheBehavior": {
"TargetOriginId": "S3-static-assets",
"ViewerProtocolPolicy": "redirect-to-https",
"TrustedSigners": {
"Enabled": false,
"Quantity": 0
},
"ForwardedValues": {
"QueryString": false,
"Cookies": {
"Forward": "none"
}
},
"MinTTL": 0,
"DefaultTTL": 86400,
"MaxTTL": 31536000
},
"Comment": "Static assets CDN",
"Enabled": true
}'
# 3. Cache Behaviors for Different Asset Types
# CSS and JS files
aws cloudfront create-cache-behavior \
--distribution-id DISTRIBUTION_ID \
--path-pattern "/static/css/*" \
--target-origin-id "S3-static-assets" \
--viewer-protocol-policy "redirect-to-https" \
--min-ttl 0 \
--default-ttl 31536000 \
--max-ttl 31536000 \
--compress true
# Images
aws cloudfront create-cache-behavior \
--distribution-id DISTRIBUTION_ID \
--path-pattern "/static/images/*" \
--target-origin-id "S3-static-assets" \
--viewer-protocol-policy "redirect-to-https" \
--min-ttl 0 \
--default-ttl 604800 \
--max-ttl 31536000
# 4. Origin Access Identity (OAI)
aws cloudfront create-cloud-front-origin-access-identity \
--cloud-front-origin-access-identity-config '{
"CallerReference": "static-assets-oai-'$(date +%s)'",
"Comment": "OAI for static assets"
}'
# Update S3 bucket policy
aws s3api put-bucket-policy \
--bucket your-static-assets-bucket \
--policy '{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCloudFrontServicePrincipal",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity OAI_ID"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::your-static-assets-bucket/*"
}
]
}'
# 5. CloudFront Functions (Edge Computing)
# Create function for asset optimization
function handler(event) {
var request = event.request;
var uri = request.uri;
// Add cache headers for static assets
if (uri.startsWith('/static/')) {
request.headers['cache-control'] = {
value: 'public, max-age=31536000, immutable'
};
}
return request;
}
# 6. Lambda@Edge for Advanced Processing
# Lambda function for dynamic asset processing
exports.handler = (event, context, callback) => {
const request = event.Records[0].cf.request;
const uri = request.uri;
// WebP conversion for images
if (uri.match(/\.(jpg|jpeg|png)$/)) {
const acceptHeader = request.headers['accept'];
if (acceptHeader && acceptHeader[0].value.includes('image/webp')) {
request.uri = uri.replace(/\.(jpg|jpeg|png)$/, '.webp');
}
}
callback(null, request);
};
# 7. CloudFront Invalidation
# Invalidate specific files
aws cloudfront create-invalidation \
--distribution-id DISTRIBUTION_ID \
--paths "/static/css/main.css" "/static/js/app.js"
# Invalidate entire directory
aws cloudfront create-invalidation \
--distribution-id DISTRIBUTION_ID \
--paths "/static/*"
Google Cloud CDN
Google Cloud CDN Setup
Google Cloud CDN Implementation
# Google Cloud CDN Setup
# 1. Create Storage Bucket
gsutil mb gs://your-static-assets-bucket
gsutil web set -m index.html -e 404.html gs://your-static-assets-bucket
# Upload static assets
gsutil -m cp -r ./static/* gs://your-static-assets-bucket/static/
gsutil -m cp -r ./images/* gs://your-static-assets-bucket/images/
# Make bucket publicly readable
gsutil iam ch allUsers:objectViewer gs://your-static-assets-bucket
# 2. Create Load Balancer
gcloud compute backend-buckets create static-assets-backend \
--gcs-bucket-name=your-static-assets-bucket
# 3. Create URL Map
gcloud compute url-maps create static-assets-map \
--default-backend-bucket=static-assets-backend
# 4. Create Target HTTP Proxy
gcloud compute target-http-proxies create static-assets-proxy \
--url-map=static-assets-map
# 5. Create Forwarding Rule
gcloud compute forwarding-rules create static-assets-rule \
--global \
--target-http-proxy=static-assets-proxy \
--ports=80
# 6. Enable Cloud CDN
gcloud compute backend-buckets update static-assets-backend \
--enable-cdn \
--cache-mode=CACHE_ALL_STATIC \
--default-ttl=3600 \
--max-ttl=86400
# 7. Cache Policies
# Set cache policies for different content types
gcloud compute url-maps add-path-matcher static-assets-map \
--path-matcher-name=static-matcher \
--path-rules="css/*=static-assets-backend,js/*=static-assets-backend,images/*=static-assets-backend"
# 8. Custom Cache Headers
# Cloud Function for custom headers
exports.addCacheHeaders = (req, res) => {
const url = req.url;
if (url.startsWith('/static/')) {
res.set({
'Cache-Control': 'public, max-age=31536000, immutable',
'CDN-Cache-Control': 'max-age=31536000'
});
} else if (url.match(/\.(jpg|jpeg|png|gif|webp)$/)) {
res.set({
'Cache-Control': 'public, max-age=604800',
'CDN-Cache-Control': 'max-age=604800'
});
}
res.send('OK');
};
# 9. CDN Monitoring
# Monitor CDN performance
gcloud logging read "resource.type=cdn" --limit=50
# Check cache hit rates
gcloud compute backend-buckets describe static-assets-backend \
--global
Asset Optimization
Static Asset Optimization
Asset Optimization Strategies
# Static Asset Optimization
# 1. Asset Bundling and Minification
# Webpack configuration for asset optimization
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
entry: {
main: './src/js/main.js',
styles: './src/css/styles.css'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'static/js/[name].[contenthash].js',
clean: true
},
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true
}
}
}),
new CssMinimizerPlugin()
],
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
},
plugins: [
new MiniCssExtractPlugin({
filename: 'static/css/[name].[contenthash].css'
})
]
};
# 2. Image Optimization Pipeline
# Image optimization script
#!/bin/bash
# optimize-images.sh
INPUT_DIR="./src/images"
OUTPUT_DIR="./dist/static/images"
QUALITY=85
mkdir -p $OUTPUT_DIR
# Optimize JPEG images
find $INPUT_DIR -name "*.jpg" -o -name "*.jpeg" | while read file; do
filename=$(basename "$file")
echo "Optimizing $filename..."
# Resize and compress
convert "$file" \
-resize "1920x>" \
-quality $QUALITY \
-strip \
-interlace Plane \
"$OUTPUT_DIR/$filename"
# Generate WebP version
cwebp -q $QUALITY "$file" -o "$OUTPUT_DIR/${filename%.*}.webp"
done
# Optimize PNG images
find $INPUT_DIR -name "*.png" | while read file; do
filename=$(basename "$file")
echo "Optimizing $filename..."
# Compress PNG
pngquant --quality=65-80 --ext .png --force "$file"
# Generate WebP version
cwebp -q $QUALITY "$file" -o "$OUTPUT_DIR/${filename%.*}.webp"
done
# 3. Font Optimization
# Font subsetting and optimization
# Using fonttools for subsetting
pip install fonttools[woff]
# Subset fonts to include only used characters
pyftsubset \
--text-file=used-characters.txt \
--output-file=subset-font.woff2 \
--flavor=woff2 \
--layout-features=* \
original-font.woff2
# 4. Asset Versioning Strategy
# Generate asset manifest
const fs = require('fs');
const path = require('path');
function generateAssetManifest() {
const manifest = {};
const distDir = './dist/static';
// CSS files
const cssFiles = fs.readdirSync(path.join(distDir, 'css'));
cssFiles.forEach(file => {
const hash = file.match(/\.([a-f0-9]+)\.css$/)?.[1];
if (hash) {
const name = file.replace(`.${hash}.css`, '');
manifest[`css/${name}.css`] = `css/${file}`;
}
});
// JS files
const jsFiles = fs.readdirSync(path.join(distDir, 'js'));
jsFiles.forEach(file => {
const hash = file.match(/\.([a-f0-9]+)\.js$/)?.[1];
if (hash) {
const name = file.replace(`.${hash}.js`, '');
manifest[`js/${name}.js`] = `js/${file}`;
}
});
fs.writeFileSync(
'./dist/asset-manifest.json',
JSON.stringify(manifest, null, 2)
);
}
generateAssetManifest();
# 5. Asset Loading Optimization
# Critical CSS inlining
const critical = require('critical');
critical.generate({
inline: true,
base: 'dist/',
src: 'index.html',
dest: 'index.html',
width: 1300,
height: 900,
minify: true,
extract: true
});
# 6. Preload Critical Assets
# HTML template with preloading
# 7. Service Worker for Asset Caching
# Service worker for offline asset serving
const CACHE_NAME = 'static-assets-v1';
const STATIC_ASSETS = [
'/static/css/main.css',
'/static/js/main.js',
'/static/images/logo.png'
];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => cache.addAll(STATIC_ASSETS))
);
});
self.addEventListener('fetch', (event) => {
if (event.request.url.includes('/static/')) {
event.respondWith(
caches.match(event.request)
.then((response) => {
if (response) {
return response;
}
return fetch(event.request);
})
);
}
});
CDN Monitoring
CDN Performance Monitoring
CDN Monitoring Setup
# CDN Performance Monitoring
# 1. Cloudflare Analytics
# Monitor CDN performance via Cloudflare API
curl -X GET "https://api.cloudflare.com/client/v4/zones/ZONE_ID/analytics/dashboard" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json"
# Custom analytics script
class CloudflareAnalytics {
constructor(zoneId, apiToken) {
this.zoneId = zoneId;
this.apiToken = apiToken;
this.baseUrl = 'https://api.cloudflare.com/client/v4';
}
async getAnalytics(startDate, endDate) {
const response = await fetch(
`${this.baseUrl}/zones/${this.zoneId}/analytics/dashboard?since=${startDate}&until=${endDate}`,
{
headers: {
'Authorization': `Bearer ${this.apiToken}`,
'Content-Type': 'application/json'
}
}
);
return await response.json();
}
async getCacheHitRate(startDate, endDate) {
const analytics = await this.getAnalytics(startDate, endDate);
const totalRequests = analytics.result.totals.requests.all;
const cachedRequests = analytics.result.totals.requests.cached;
return (cachedRequests / totalRequests) * 100;
}
}
# 2. AWS CloudFront Monitoring
# CloudWatch metrics for CloudFront
aws cloudwatch get-metric-statistics \
--namespace AWS/CloudFront \
--metric-name Requests \
--dimensions Name=DistributionId,Value=DISTRIBUTION_ID \
--start-time 2024-01-01T00:00:00Z \
--end-time 2024-01-02T00:00:00Z \
--period 3600 \
--statistics Sum
# CloudFront real-time monitoring
aws cloudwatch get-metric-statistics \
--namespace AWS/CloudFront \
--metric-name CacheHitRate \
--dimensions Name=DistributionId,Value=DISTRIBUTION_ID \
--start-time 2024-01-01T00:00:00Z \
--end-time 2024-01-02T00:00:00Z \
--period 300 \
--statistics Average
# 3. Custom CDN Monitoring
class CDNMonitor {
constructor() {
this.metrics = {
requests: 0,
cacheHits: 0,
cacheMisses: 0,
errors: 0,
responseTime: []
};
}
recordRequest(isCacheHit, responseTime, hasError = false) {
this.metrics.requests++;
if (isCacheHit) {
this.metrics.cacheHits++;
} else {
this.metrics.cacheMisses++;
}
if (hasError) {
this.metrics.errors++;
}
this.metrics.responseTime.push(responseTime);
}
getHitRate() {
const total = this.metrics.cacheHits + this.metrics.cacheMisses;
return total > 0 ? (this.metrics.cacheHits / total) * 100 : 0;
}
getAverageResponseTime() {
const total = this.metrics.responseTime.reduce((sum, time) => sum + time, 0);
return total / this.metrics.responseTime.length;
}
getStats() {
return {
totalRequests: this.metrics.requests,
cacheHitRate: this.getHitRate(),
averageResponseTime: this.getAverageResponseTime(),
errorRate: (this.metrics.errors / this.metrics.requests) * 100
};
}
}
# 4. Performance Testing
# CDN performance testing script
const https = require('https');
const { performance } = require('perf_hooks');
class CDNPerformanceTest {
constructor(cdnUrl, originUrl) {
this.cdnUrl = cdnUrl;
this.originUrl = originUrl;
}
async testAsset(assetPath) {
const cdnStart = performance.now();
const cdnResponse = await this.fetchAsset(`${this.cdnUrl}${assetPath}`);
const cdnTime = performance.now() - cdnStart;
const originStart = performance.now();
const originResponse = await this.fetchAsset(`${this.originUrl}${assetPath}`);
const originTime = performance.now() - originStart;
return {
asset: assetPath,
cdnTime: cdnTime,
originTime: originTime,
improvement: ((originTime - cdnTime) / originTime) * 100
};
}
async fetchAsset(url) {
return new Promise((resolve, reject) => {
https.get(url, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => resolve({ status: res.statusCode, data }));
}).on('error', reject);
});
}
async runTests(assets) {
const results = [];
for (const asset of assets) {
const result = await this.testAsset(asset);
results.push(result);
}
return results;
}
}
# 5. CDN Health Check
class CDNHealthCheck {
constructor(cdnUrl) {
this.cdnUrl = cdnUrl;
}
async checkHealth() {
try {
const response = await fetch(`${this.cdnUrl}/health-check`);
if (response.ok) {
return {
status: 'healthy',
responseTime: response.headers.get('x-response-time'),
timestamp: new Date().toISOString()
};
} else {
return {
status: 'unhealthy',
error: `HTTP ${response.status}`,
timestamp: new Date().toISOString()
};
}
} catch (error) {
return {
status: 'unhealthy',
error: error.message,
timestamp: new Date().toISOString()
};
}
}
async runContinuousCheck(interval = 60000) {
setInterval(async () => {
const health = await this.checkHealth();
console.log('CDN Health:', health);
if (health.status === 'unhealthy') {
// Send alert
this.sendAlert(health);
}
}, interval);
}
sendAlert(health) {
console.error('CDN Alert:', health);
// Implement alerting logic (email, Slack, etc.)
}
}
# 6. CDN Dashboard
class CDNDashboard {
constructor(monitors) {
this.monitors = monitors;
}
async generateReport() {
const report = {
timestamp: new Date().toISOString(),
metrics: {}
};
for (const [name, monitor] of Object.entries(this.monitors)) {
report.metrics[name] = await monitor.getStats();
}
return report;
}
async renderDashboard() {
const report = await this.generateReport();
console.log('=== CDN Performance Dashboard ===');
console.log(`Report Generated: ${report.timestamp}`);
console.log('');
for (const [name, metrics] of Object.entries(report.metrics)) {
console.log(`${name.toUpperCase()}:`);
console.log(` Cache Hit Rate: ${metrics.cacheHitRate.toFixed(2)}%`);
console.log(` Average Response Time: ${metrics.averageResponseTime.toFixed(2)}ms`);
console.log(` Total Requests: ${metrics.totalRequests}`);
console.log(` Error Rate: ${metrics.errorRate.toFixed(2)}%`);
console.log('');
}
}
}
Best Practices
CDN Optimization Guidelines
CDN Best Practices
- Use appropriate cache TTL values
- Implement cache invalidation
- Optimize asset sizes
- Use compression
- Monitor performance metrics
- Implement fallback strategies
- Use HTTP/2 and HTTP/3
Common Mistakes
- Incorrect cache headers
- No cache invalidation
- Over-caching dynamic content
- Poor asset optimization
- No monitoring setup
- Missing fallback mechanisms
- Inadequate security measures
Summary
CDN setup for static assets involves several key components:
- CDN Providers: Cloudflare, AWS CloudFront, Google Cloud CDN
- Configuration: Cache policies, compression, SSL/TLS
- Asset Optimization: Minification, compression, versioning
- Monitoring: Performance metrics, health checks, analytics
- Best Practices: Cache strategies, invalidation, fallbacks
Need More Help?
Struggling with CDN setup or need help optimizing your static assets? Our performance experts can help you implement effective CDN solutions.
Get CDN Help