Improving site load times and Core Web Vitals
Scope: Netlify performance optimization, CDN caching, build optimization, and cost reduction Lines: ~310 Last Updated: 2025-10-18
Activate this skill when:
Global edge network:
Caching behavior:
Cache-Control: public, max-age=0, must-revalidateBuild minutes are limited:
Optimization strategies:
Key metrics:
# netlify.toml - Cache optimization
[[headers]]
for = "/*.html"
[headers.values]
Cache-Control = "public, max-age=0, must-revalidate"
[[headers]]
for = "/assets/*"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
[[headers]]
for = "/*.js"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
[[headers]]
for = "/*.css"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
[[headers]]
for = "/*.woff2"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
[[headers]]
for = "/images/*"
[headers.values]
Cache-Control = "public, max-age=2592000, immutable" # 30 days
[[headers]]
for = "/api/*"
[headers.values]
Cache-Control = "no-cache, no-store, must-revalidate"
Best practices:
main.abc123.js): 1 year + immutable# netlify.toml - Image optimization plugin
[[plugins]]
package = "@netlify/plugin-image-optim"
[plugins.inputs]
# Optimize quality (0-100)
quality = 85
# Generate WebP versions
formats = ["webp", "avif"]
# Resize images
maxWidth = 1920
maxHeight = 1080
Next.js Image Optimization:
// next.config.js
module.exports = {
images: {
domains: ['cdn.example.com'],
formats: ['image/avif', 'image/webp'],
deviceSizes: [640, 750, 828, 1080, 1200, 1920],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
},
};
Manual optimization:
# Use sharp for build-time optimization
npm install sharp
# Optimize during build
node scripts/optimize-images.js
When to use:
# netlify.toml - Build plugins for caching
[[plugins]]
package = "netlify-plugin-cache"
[plugins.inputs]
# Cache directories
paths = [
"node_modules",
".next/cache",
".cache",
"public/static",
]
Next.js incremental builds:
// next.config.js
module.exports = {
// Enable Next.js cache
experimental: {
outputStandalone: true,
},
};
Gatsby incremental builds:
# netlify.toml
[build.environment]
GATSBY_EXPERIMENTAL_PAGE_BUILD_ON_DATA_CHANGES = "true"
[build]
command = "npm run build"
publish = "public"
When to use:
# netlify.toml - Skip builds conditionally
[build]
command = "npm run build"
publish = "dist"
# Skip build if no relevant changes
ignore = "git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF -- src/ public/"
Skip builds for docs changes:
# Only build if src/ or config changed, not docs/
ignore = "git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF -- src/ package.json netlify.toml"
When to use:
# netlify.toml - Asset optimization
[build]
command = "npm run build && npm run optimize"
publish = "dist"
[build.processing]
skip_processing = false
[build.processing.css]
bundle = true
minify = true
[build.processing.js]
bundle = true
minify = true
[build.processing.images]
compress = true
Vite bundle optimization:
// vite.config.js
import { defineConfig } from 'vite';
export default defineConfig({
build: {
minify: 'terser',
terserOptions: {
compress: {
drop_console: true, // Remove console.log in production
},
},
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
utils: ['lodash', 'date-fns'],
},
},
},
},
});
When to use:
# netlify.toml - Prerender routes
[[redirects]]
from = "/blog/*"
to = "/blog/:splat"
status = 200
force = true
# Cache prerendered pages
headers = {Cache-Control = "public, max-age=3600, s-maxage=31536000"}
# Cache static API responses
[[redirects]]
from = "/api/static/*"
to = "/.netlify/functions/api-static/:splat"
status = 200
headers = {Cache-Control = "public, max-age=300"} # 5 min cache
On-Demand Builders (cache until redeploy):
// netlify/functions/expensive-page.js
import { builder } from '@netlify/functions';
export const handler = builder(async (event) => {
// Expensive computation, cached until next deploy
const data = await fetchExpensiveData();
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'public, max-age=0, must-revalidate',
'Netlify-CDN-Cache-Control': 'public, max-age=31536000', // CDN cache 1 year
},
body: JSON.stringify(data),
};
});
When to use:
# Analyze Next.js bundle
npm install @next/bundle-analyzer
# next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
});
module.exports = withBundleAnalyzer({
// Next.js config
});
# Run analysis
ANALYZE=true npm run build
Vite bundle analysis:
npm install rollup-plugin-visualizer
# vite.config.js
import { visualizer } from 'rollup-plugin-visualizer';
export default defineConfig({
plugins: [
visualizer({
open: true,
gzipSize: true,
brotliSize: true,
}),
],
});
When to use:
// netlify/edge-functions/cache-control.ts
import { Context } from "https://edge.netlify.com";
export default async (request: Request, context: Context) => {
const response = await context.next();
// Add caching to specific routes
if (request.url.includes('/blog/')) {
const headers = new Headers(response.headers);
headers.set('Cache-Control', 'public, max-age=3600, s-maxage=86400');
return new Response(response.body, {
status: response.status,
headers,
});
}
return response;
};
export const config = {
path: "/*",
};
When to use:
Pattern | Use Case
-------------------------------------------|---------------------------
public, max-age=0, must-revalidate | HTML pages (always fresh)
public, max-age=31536000, immutable | Hashed assets (1 year)
public, max-age=3600 | Semi-static content (1 hour)
no-cache, no-store, must-revalidate | Private/dynamic data
s-maxage=86400 | CDN cache (1 day)
✅ Enable build caching (node_modules, framework caches)
✅ Use incremental builds (Next.js, Gatsby)
✅ Skip builds for non-code changes
✅ Optimize dependencies (prune unused packages)
✅ Use build plugins efficiently
✅ Cache build artifacts between deploys
✅ Analyze bundle size regularly
✅ Remove console.log in production
Metric | Good | Needs Improvement | Poor
-----------|---------|-------------------|-------
TTFB | <200ms | 200-600ms | >600ms
FCP | <1.8s | 1.8-3.0s | >3.0s
LCP | <2.5s | 2.5-4.0s | >4.0s
CLS | <0.1 | 0.1-0.25 | >0.25
TTI | <3.8s | 3.8-7.3s | >7.3s
Asset Type | Tool | Command
-----------|------------------------|---------------------------
Images | sharp | npm install sharp
Images | imagemin | npm install imagemin
CSS | cssnano | npm install cssnano
JS | terser | npm install terser
Fonts | subset-font | npm install subset-font
SVG | svgo | npm install svgo
Plugin | Purpose
--------------------------------|---------------------------
@netlify/plugin-image-optim | Image optimization
netlify-plugin-cache | Cache node_modules
@netlify/plugin-lighthouse | Performance audits
netlify-plugin-inline-critical | Inline critical CSS
@netlify/plugin-nextjs | Next.js optimization
❌ No cache headers: Static assets fetched every time ✅ Set long cache for hashed assets, short for HTML
❌ Large bundle sizes: Slow page loads, poor UX ✅ Code split, tree shake, analyze bundles
❌ Unoptimized images: Large file sizes, slow loads ✅ Use WebP/AVIF, resize, compress, lazy load
❌ No build caching: Slow builds, wasted build minutes ✅ Cache node_modules and framework caches
❌ Building for every commit: Wasted resources ✅ Skip builds for docs/non-code changes
❌ No bundle analysis: Unknown performance bottlenecks ✅ Regularly analyze with tools (bundle-analyzer, lighthouse)
❌ Synchronous loading: Blocking render ✅ Async/defer scripts, lazy load images/components
❌ No performance monitoring: Issues go unnoticed ✅ Use Netlify Analytics, Lighthouse CI, Real User Monitoring
netlify-deployment.md - Site deployment, build configuration, continuous deploymentnetlify-functions.md - Serverless functions, Edge Functions, API optimizationfrontend-performance.md - General frontend performance patternsnextjs-seo.md - Next.js SEO and performance optimizationcdn-configuration.md - CDN strategies and caching patternsweb-vitals-optimization.md - Core Web Vitals improvement techniquesLast Updated: 2025-10-18 Format Version: 1.0 (Atomic)