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

  1. Images: Use Next.js Image component with proper sizing and formats

  2. Scripts: Load strategically with appropriate loading strategies

  3. Edge Functions: Use for geolocation, A/B testing, and fast APIs

  4. Deployment: Configure proper headers, redirects, and environment variables

  5. Monitoring: Implement performance tracking and error monitoring

  6. Security: Set appropriate security headers and CSP

  7. 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!

LecturreAI Assistant

Hello! I'm LecturreAI

What would you like to learn today?