Lesson 06-Next.js Application Routing-Performance Optimization

Image Optimization

Next.js provides a built-in <Image> component that automatically handles image optimization and responsive layouts.

// app/page.js
import Image from 'next/image';

export default function Page() {
  return (
    <div>
      <Image
        src="/path/to/image.jpg"
        alt="Sample image"
        width={800}
        height={600}
      />
    </div>
  );
}

Notes

  • Size Attributes: Ensure correct width and height attributes are provided so Next.js can generate appropriate responsive images.
  • Unoptimized Attribute: Set unoptimized to true if you do not want Next.js to handle image optimization.

Video Optimization

Use the HTML5 <video> tag to embed videos, allowing control over playback, pausing, and other behaviors.

// app/page.js
export default function Page() {
  return (
    <div>
      <video controls>
        <source src="/path/to/video.mp4" type="video/mp4" />
        Your browser does not support the video tag.
      </video>
    </div>
  );
}

Notes

  • Format Compatibility: Provide multiple video source formats to support different browsers.
  • Compression: Ensure video files are compressed before uploading to reduce file size.

Font Optimization

Using Web Fonts

Web fonts enhance a website’s visual appeal but may increase page load times. Use the following strategies to optimize font loading:

// app/page.js
import dynamic from 'next/dynamic';

const DynamicFontLink = dynamic(() => import('next/font/google'), {
  ssr: false,
});

export default function Page() {
  return (
    <div>
      <DynamicFontLink
        href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap"
        as="style"
        crossOrigin=""
      />
      <h1 style={{ fontFamily: 'Roboto', fontSize: '2rem' }}>Hello World</h1>
    </div>
  );
}

Notes

  • Lazy Loading: Use next/dynamic to load fonts asynchronously, deferring font loading until after initial page interaction.
  • Font Display Strategy: Use the font-display property (e.g., font-display: swap) to control font loading and display behavior.
  • Font Subsets: Load only the required character subsets to reduce font file size.

Metadata

Metadata, including page titles, descriptions, and keywords, is critical for SEO. Use next/head or next/document to dynamically set page metadata.

// app/page.js
import Head from 'next/head';

export default function Page() {
  return (
    <>
      <Head>
        <title>My Page Title</title>
        <meta name="description" content="A description of my page" />
        <meta name="keywords" content="keyword1, keyword2" />
      </Head>
      <h1>Welcome to My App</h1>
      <p>This is a sample page.</p>
    </>
  );
}

Script Optimization

Optimizing script loading can significantly improve page load speed and performance.

Using next/script

The next/script component allows control over script loading timing.

// app/page.js
import Script from 'next/script';

export default function Page() {
  return (
    <>
      <Script
        strategy="beforeInteractive"
        src="https://example.com/my-script.js"
      />
      <h1>Welcome to My App</h1>
      <p>This is a sample page.</p>
    </>
  );
}

Strategy Attribute

  • beforeInteractive: Script loads before document parsing is complete.
  • afterInteractive: Script loads after document parsing is complete.
  • lazyOnload: Script loads during browser idle time.

Bundle Analyzer

A bundle analyzer helps understand the application’s bundle output, identifying areas for optimization.

Using next/dist/build/webpack/loaders/bundle-analyzer-plugin

  • Install webpack-bundle-analyzer:
npm install --save-dev webpack-bundle-analyzer
  • Configure next.config.js:
// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true',
});

module.exports = withBundleAnalyzer({});
  • Run the analysis:
NEXT_PUBLIC_ANALYZE=true next build

Lazy Loading

Lazy loading (also known as deferred loading) improves initial page load speed by loading non-critical resources only when needed.

Using Intersection Observer API

The Intersection Observer API detects when an element enters the viewport, triggering lazy loading.

// app/page.js
import Image from 'next/image';
import { useRef, useState } from 'react';
import { useIntersectionObserver } from 'react-intersection-observer';

function LazyImage({ src, alt }) {
  const [isVisible, setIsVisible] = useState(false);
  const ref = useRef(null);

  const handleIntersection = (entries, observer) => {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        setIsVisible(true);
        observer.disconnect();
      }
    });
  };

  useIntersectionObserver(ref, handleIntersection, { threshold: 0.1 });

  return (
    <div ref={ref}>
      {isVisible ? (
        <Image
          src={src}
          alt={alt}
          width={300}
          height={200}
        />
      ) : (
        <div>Loading...</div>
      )}
    </div>
  );
}

export default function Page() {
  return (
    <div>
      <LazyImage
        src="/path/to/image.jpg"
        alt="Sample image"
      />
    </div>
  );
}

OpenTelemetry

OpenTelemetry is a framework for collecting, processing, and exporting telemetry data (e.g., metrics, logs, and traces). In Next.js, use OpenTelemetry to collect application performance data.

Install OpenTelemetry

npm install @opentelemetry/api @opentelemetry/sdk-trace-web @opentelemetry/instrumentation-fetch

Initialize OpenTelemetry

// telemetry.js
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
import { ConsoleSpanExporter } from '@opentelemetry/exporter-console';

const provider = new NodeTracerProvider();
provider.addSpanProcessor(new BatchSpanProcessor(new ConsoleSpanExporter()));
provider.register();

// Instrument the fetch API
import { registerInstrumentations } from '@opentelemetry/instrumentation';
registerInstrumentations({
  instrumentations: [getNodeAutoInstrumentations()],
});

Using Tracer

// app/page.js
import { trace } from '@opentelemetry/api';

const tracer = trace.getTracer('my-app');

export default function Page() {
  const span = tracer.startSpan('my-operation');
  span.end();

  return (
    <div>
      <h1>Welcome to My App</h1>
      <p>This is a sample page.</p>
    </div>
  );
}

Static Assets in Public

The public directory stores static assets like images and fonts, accessible directly via the / path.

Example

Assume a logo.png image file is located in the public directory.

// app/page.js
import Image from 'next/image';

export default function Page() {
  return (
    <div>
      <Image
        src="/logo.png"
        alt="Logo"
        width={100}
        height={100}
      />
    </div>
  );
}

Third-Party Libraries

Third-party libraries can significantly impact application performance. Choose lightweight libraries and load only required modules.

Assume you are using a library named my-third-party-library.

// app/page.js
import { specificFunction } from 'my-third-party-library';

export default function Page() {
  specificFunction();

  return (
    <div>
      <h1>Welcome to My App</h1>
      <p>This is a sample page.</p>
    </div>
  );
}

Memory Usage

Analyzing memory usage helps identify memory leaks and other performance issues.

Using Chrome DevTools

Chrome DevTools provides memory analysis tools to inspect memory usage.

  • Open DevTools: Right-click the page and select “Inspect.”
  • Go to the Memory panel.
  • Take a memory snapshot: Click the “Take snapshot” button.

Using Node.js Memory Analysis Tools

For server-side memory analysis, use Node.js built-in tools or third-party libraries like memwatch-next.

// server.js
require('memwatch-next');

// ... your server code ...
Share your love