React & Next.js Topic
Next.js Deployment, Optimization & Best Practices
1. Image Optimization
Next.js Image Component
jsx
import Image from 'next/image';
import { useState } from 'react';
function OptimizedImageGallery() {
const [isLoading, setIsLoading] = useState(true);
return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{/* Basic optimized image */}
<div className="relative aspect-video">
<Image
src="/hero-image.jpg"
alt="Hero image description"
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
priority // For above-the-fold images
className={`object-cover transition-opacity duration-300 ${
isLoading ? 'opacity-0' : 'opacity-100'
}`}
onLoad={() => setIsLoading(false)}
/>
{isLoading && (
<div className="absolute inset-0 bg-gray-200 animate-pulse" />
)}
</div>
{/* External image with optimization */}
<Image
src="https://example.com/external-image.jpg"
alt="External image"
width={600}
height={400}
placeholder="blur"
blurDataURL="data:image/jpeg;base64,/9j/"
/>
{/* Responsive image with multiple sizes */}
<Image
src="/product-image.png"
alt="Product image"
width={800}
height={600}
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 800px"
quality={85} // Balance between quality and size
loading="lazy" // For below-the-fold images
/>
</div>
);
}
Custom Image Loader
jsx
// lib/image-loader.js
export const cloudinaryLoader = ({ src, width, quality }) => {
const params = ['f_auto', 'q_auto', 'c_limit', `w_${width}`];
if (quality) {
params.push(`q_${quality}`);
}
return `https://res.cloudinary.com/your-cloud/image/upload/${params.join(',')}/${src}`;
};
// next.config.js
module.exports = {
images: {
domains: ['example.com', 'res.cloudinary.com'],
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
formats: ['image/webp', 'image/avif'],
minimumCacheTTL: 60 * 60 * 24 * 30, // 30 days
},
};
// Usage with custom loader
<Image
loader={cloudinaryLoader}
src="your-image-path.jpg"
alt="Description"
width={600}
height={400}
/>
Advanced Image Optimization
jsx
// components/LazyImage.jsx
'use client';
import { useState, useRef, useEffect } from 'react';
import Image from 'next/image';
function LazyImage({ src, alt, width, height, ...props }) {
const [isVisible, setIsVisible] = useState(false);
const [imageLoaded, setImageLoaded] = useState(false);
const imgRef = useRef();
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsVisible(true);
observer.disconnect();
}
},
{ threshold: 0.1 }
);
if (imgRef.current) {
observer.observe(imgRef.current);
}
return () => observer.disconnect();
}, []);
return (
<div ref={imgRef} className="relative">
{isVisible ? (
<>
<Image
src={src}
alt={alt}
width={width}
height={height}
onLoad={() => setImageLoaded(true)}
className={`transition-opacity duration-300 ${
imageLoaded ? 'opacity-100' : 'opacity-0'
}`}
{...props}
/>
{!imageLoaded && (
<div className="absolute inset-0 bg-gray-200 animate-pulse" />
)}
</>
) : (
<div
className="bg-gray-200 animate-pulse"
style={{ width, height }}
/>
)}
</div>
);
}
2. Script Optimization
Next.js Script Component
jsx
import Script from 'next/script';
import { useState } from 'react';
function AnalyticsWrapper() {
const [analyticsLoaded, setAnalyticsLoaded] = useState(false);
return (
<>
{/* Third-party scripts with different strategies */}
<Script
src="https://www.google-analytics.com/analytics.js"
strategy="afterInteractive" // Load after page becomes interactive
onLoad={() => {
console.log('Google Analytics loaded');
setAnalyticsLoaded(true);
}}
onError={(e) => {
console.error('Google Analytics failed to load', e);
}}
/>
<Script
src="https://connect.facebook.net/en_US/sdk.js"
strategy="lazyOnload" // Load during idle time
onLoad={() => {
console.log('Facebook SDK loaded');
// Initialize FB SDK
window.fbAsyncInit = function() {
FB.init({
appId: 'your-app-id',
cookie: true,
xfbml: true,
version: 'v15.0'
});
};
}}
/>
{/* Inline script */}
<Script
id="theme-script"
strategy="beforeInteractive" // Load before page is interactive
dangerouslySetInnerHTML={{
__html: `
(function() {
const theme = localStorage.getItem('theme') || 'light';
document.documentElement.setAttribute('data-theme', theme);
})();
`,
}}
/>
{/* Web Workers */}
<Script
src="/workers/image-processor.js"
strategy="worker"
/>
</>
);
}
Performance Monitoring Scripts
jsx
// components/PerformanceMonitor.jsx
import Script from 'next/script';
export function PerformanceMonitor() {
return (
<Script
id="performance-monitor"
strategy="afterInteractive"
dangerouslySetInnerHTML={{
__html: `
// Core Web Vitals monitoring
const vitalsUrl = 'https://vitals.vercel-analytics.com/v1/vitals';
function getConnectionSpeed() {
return 'connection' in navigator &&
navigator['connection'] &&
'effectiveType' in navigator['connection']
? navigator['connection']['effectiveType']
: '';
}
function sendToAnalytics(metric, options) {
const body = {
dsn: options.analyticsId,
id: metric.id,
page: window.location.pathname,
href: window.location.href,
event_name: metric.name,
value: metric.value.toString(),
speed: getConnectionSpeed(),
};
const blob = new Blob([new URLSearchParams(body).toString()], {
type: 'application/x-www-form-urlencoded',
});
if (navigator.sendBeacon) {
navigator.sendBeacon(vitalsUrl, blob);
} else {
fetch(vitalsUrl, {
body: blob,
method: 'POST',
credentials: 'omit',
keepalive: true,
});
}
}
// Initialize web vitals tracking
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS((metric) => sendToAnalytics(metric, { analyticsId: 'your-id' }));
getFID((metric) => sendToAnalytics(metric, { analyticsId: 'your-id' }));
getFCP((metric) => sendToAnalytics(metric, { analyticsId: 'your-id' }));
getLCP((metric) => sendToAnalytics(metric, { analyticsId: 'your-id' }));
getTTFB((metric) => sendToAnalytics(metric, { analyticsId: 'your-id' }));
});
`,
}}
/>
);
}
Dynamic Script Loading
jsx
// hooks/useDynamicScript.js
import { useEffect, useState } from 'react';
export function useDynamicScript(url) {
const [ready, setReady] = useState(false);
const [failed, setFailed] = useState(false);
useEffect(() => {
if (!url) return;
const element = document.createElement('script');
element.src = url;
element.type = 'text/javascript';
element.async = true;
element.onload = () => {
setReady(true);
};
element.onerror = () => {
setReady(false);
setFailed(true);
};
document.head.appendChild(element);
return () => {
document.head.removeChild(element);
};
}, [url]);
return {
ready,
failed,
};
}
// components/DynamicComponent.jsx
import { useDynamicScript } from '../hooks/useDynamicScript';
function DynamicComponent({ scriptUrl, componentName }) {
const { ready, failed } = useDynamicScript(scriptUrl);
if (!ready) {
return <div>Loading component...</div>;
}
if (failed) {
return <div>Failed to load component</div>;
}
const Component = window[componentName];
return Component ? <Component /> : <div>Component not found</div>;
}
3. Edge Functions
Next.js Edge Runtime
jsx
// middleware.js (Edge Middleware)
import { NextResponse } from 'next/server';
export const config = {
runtime: 'edge',
matcher: [
'/api/:path*',
'/dashboard/:path*',
],
};
export function middleware(request) {
// Geo-based routing
const country = request.geo?.country || 'US';
const city = request.geo?.city || 'Unknown';
// A/B testing at the edge
const url = request.nextUrl;
const variant = Math.random() > 0.5 ? 'a' : 'b';
if (url.pathname.startsWith('/home')) {
url.pathname = `/home/${variant}`;
return NextResponse.rewrite(url);
}
// Add geo headers to API requests
if (url.pathname.startsWith('/api')) {
const requestHeaders = new Headers(request.headers);
requestHeaders.set('x-country', country);
requestHeaders.set('x-city', city);
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
}
return NextResponse.next();
}
Edge API Routes
jsx
// app/api/analytics/route.js
export const runtime = 'edge';
export async function POST(request) {
try {
const body = await request.json();
// Process analytics data at the edge
const processedData = {
...body,
timestamp: new Date().toISOString(),
region: request.geo?.region || 'unknown',
country: request.geo?.country || 'unknown',
};
// Store in edge cache or send to main DB
await storeInEdgeCache(processedData);
return new Response(JSON.stringify({ success: true }), {
status: 200,
headers: {
'Content-Type': 'application/json',
'Cache-Control': 's-maxage=60', // Cache for 60 seconds
},
});
} catch (error) {
return new Response(JSON.stringify({ error: 'Processing failed' }), {
status: 500,
});
}
}
async function storeInEdgeCache(data) {
// Example: Store in Vercel KV (Edge storage)
// await kv.set(`analytics:${Date.now()}`, JSON.stringify(data));
}
Edge-based Feature Flags
jsx
// lib/edge-flags.js
export class EdgeFeatureFlags {
constructor(request) {
this.request = request;
this.country = request.geo?.country;
this.userAgent = request.headers.get('user-agent');
}
isEnabled(featureName) {
const flags = {
'new-ui': {
enabled: true,
countries: ['US', 'CA', 'GB'], // Rollout to specific countries
percentage: 50, // 50% rollout
},
'beta-feature': {
enabled: false,
users: ['user123', 'user456'], // Specific users
},
};
const flag = flags[featureName];
if (!flag) return false;
// Country-based rollout
if (flag.countries && !flag.countries.includes(this.country)) {
return false;
}
// Percentage-based rollout
if (flag.percentage) {
const userHash = this.getUserHash();
return userHash % 100 < flag.percentage;
}
return flag.enabled;
}
getUserHash() {
// Simple hash based on IP + User-Agent
const ip = this.request.ip || 'unknown';
const str = ip + this.userAgent;
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = ((hash << 5) - hash) + str.charCodeAt(i);
hash |= 0; // Convert to 32bit integer
}
return Math.abs(hash);
}
}
// Usage in middleware
export function middleware(request) {
const flags = new EdgeFeatureFlags(request);
if (flags.isEnabled('new-ui')) {
const url = request.nextUrl;
url.searchParams.set('ui', 'new');
return NextResponse.rewrite(url);
}
return NextResponse.next();
}
4. Deployment to Vercel & Other Platforms
Vercel Deployment
jsx
// vercel.json
{
"version": 2,
"buildCommand": "npm run build",
"outputDirectory": ".next",
"installCommand": "npm install",
"functions": {
"pages/api/*.js": {
"maxDuration": 30
}
},
"headers": [
{
"source": "/(.*)",
"headers": [
{
"key": "X-Content-Type-Options",
"value": "nosniff"
},
{
"key": "X-Frame-Options",
"value": "DENY"
},
{
"key": "X-XSS-Protection",
"value": "1; mode=block"
}
]
},
{
"source": "/api/(.*)",
"headers": [
{
"key": "Cache-Control",
"value": "s-maxage=60, stale-while-revalidate=300"
}
]
}
],
"rewrites": [
{
"source": "/blog/:slug",
"destination": "/blog/post?slug=:slug"
}
],
"redirects": [
{
"source": "/old-page",
"destination": "/new-page",
"permanent": true
}
]
}
Environment-specific Configuration
jsx
// lib/config.js
export const config = {
// Public environment variables (exposed to browser)
public: {
appUrl: process.env.NEXT_PUBLIC_APP_URL,
apiUrl: process.env.NEXT_PUBLIC_API_URL,
environment: process.env.NEXT_PUBLIC_ENVIRONMENT,
},
// Server-only environment variables
server: {
databaseUrl: process.env.DATABASE_URL,
secretKey: process.env.SECRET_KEY,
stripeKey: process.env.STRIPE_SECRET_KEY,
},
// Feature flags
features: {
isAnalyticsEnabled: process.env.NEXT_PUBLIC_ANALYTICS_ENABLED === 'true',
isBetaEnabled: process.env.NEXT_PUBLIC_BETA_FEATURES === 'true',
},
};
// next.config.js
module.exports = {
env: {
CUSTOM_KEY: process.env.CUSTOM_KEY,
},
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'X-Frame-Options',
value: 'DENY',
},
{
key: 'X-XSS-Protection',
value: '1; mode=block',
},
],
},
];
},
async redirects() {
return [
{
source: '/old-blog/:slug',
destination: '/blog/:slug',
permanent: true,
},
];
},
};
Multi-platform Deployment
yaml
# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Build application
run: npm run build
env:
NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
- name: Deploy to Vercel
uses: vercel/action@v28
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
scope: ${{ secrets.VERCEL_ORG_ID }}
# Alternative: Deploy to Netlify
- name: Deploy to Netlify
uses: netlify/actions/cli@master
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
with:
args: deploy --prod --dir=out
Performance Monitoring
jsx
// lib/performance.js
export class PerformanceMonitor {
static mark(name) {
if (typeof window !== 'undefined' && 'performance' in window) {
performance.mark(name);
}
}
static measure(name, startMark, endMark) {
if (typeof window !== 'undefined' && 'performance' in window) {
performance.measure(name, startMark, endMark);
const measures = performance.getEntriesByName(name);
const lastMeasure = measures[measures.length - 1];
// Send to analytics
this.sendToAnalytics(name, lastMeasure.duration);
}
}
static sendToAnalytics(metricName, duration) {
// Send to your analytics service
if (typeof window !== 'undefined' && window.gtag) {
window.gtag('event', 'performance', {
event_category: 'Performance',
event_label: metricName,
value: Math.round(duration),
});
}
}
}
// Usage in components
export function PageComponent() {
useEffect(() => {
PerformanceMonitor.mark('page-component-start');
// Component logic...
PerformanceMonitor.mark('page-component-end');
PerformanceMonitor.measure('page-component-render', 'page-component-start', 'page-component-end');
}, []);
return <div>Page Content</div>;
}
Bundle Analysis
jsx
// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
});
module.exports = withBundleAnalyzer({
experimental: {
optimizeCss: true,
},
compiler: {
removeConsole: process.env.NODE_ENV === 'production',
},
poweredByHeader: false,
compress: true,
});
// package.json scripts
{
"scripts": {
"build": "next build",
"analyze": "ANALYZE=true next build",
"build:analyze": "npm run build && npm run analyze"
}
}
Security Headers
jsx
// next.config.js
const securityHeaders = [
{
key: 'X-DNS-Prefetch-Control',
value: 'on'
},
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload'
},
{
key: 'X-XSS-Protection',
value: '1; mode=block'
},
{
key: 'X-Frame-Options',
value: 'DENY'
},
{
key: 'X-Content-Type-Options',
value: 'nosniff'
},
{
key: 'Referrer-Policy',
value: 'origin-when-cross-origin'
},
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=()'
}
];
module.exports = {
async headers() {
return [
{
source: '/(.*)',
headers: securityHeaders,
}
];
},
};
Best Practices Summary
-
Images: Use Next.js Image component with proper sizing and formats
-
Scripts: Load strategically with appropriate loading strategies
-
Edge Functions: Use for geolocation, A/B testing, and fast APIs
-
Deployment: Configure proper headers, redirects, and environment variables
-
Monitoring: Implement performance tracking and error monitoring
-
Security: Set appropriate security headers and CSP
-
Optimization: Use bundle analysis and remove console in production
These practices ensure your Next.js application is fast, secure, and scalable across different deployment platforms.
Comments (0)
No comments yet. Be the first to comment!