[Next.js Tutorial] Optimizing Images, Fonts, and Scripts: Speed Up Your Page Load

In the modern web development world, speed is not just an advantage, it's a necessity. A slow-loading website not only frustrates users but is also penalized by search engines like Google, directly affecting SEO rankings and revenue. Fortunately, with Next.js, we are equipped with extremely powerful tools to tackle core performance issues.

Optimizing Images, Fonts, and Scripts in Nextjs

This article dives into the three main culprits of slow websites: Images, Fonts, and third-party Scripts. We'll explore how to tame them using Next.js's specialized components: next/image, next/font, and next/script. Get ready to take your Next.js app to the next level in speed and user experience! ⚡️

1. next/image: The Image Optimization "Wizard" 🖼️

Images are often the heaviest assets on a web page. Loading a 5MB photo for a mobile screen is a huge waste of resources. The next/image component was created to solve this problem intelligently and automatically. It's not just a regular <img> tag, but a comprehensive image optimization engine.

The Problems with Traditional <img> Tags

The <img> tag has several weaknesses if not optimized:

  • Loads original size: Loads a 4K image for a 300px-wide div, wasting bandwidth.
  • No default lazy loading: All images are loaded immediately, even those at the bottom of the page, slowing down initial load.
  • Layout Shift (CLS): The browser doesn't know the image size until it's loaded, causing annoying "page jumps" as content shifts.
  • No automatic format conversion: Doesn't take advantage of modern formats like WebP or AVIF for better compression.

The Power of next/image

next/image solves all these issues "magically":

  • Automatic Resizing: Next.js automatically generates multiple versions of an image at different sizes and serves the most appropriate one for the user's device.
  • Default Lazy Loading: Images are only loaded when the user scrolls near them, significantly speeding up initial page load.
  • Prevents Layout Shift: By requiring width and height, next/image reserves space for the image before it loads, eliminating Cumulative Layout Shift (CLS).
  • Modern Image Formats: Automatically serves images in WebP or AVIF if the browser supports them, reducing file size without sacrificing quality.
  • Priority Loading: For important above-the-fold images (like hero banners), you can add the priority prop to tell Next.js to load them first, greatly improving Largest Contentful Paint (LCP).

How to Use next/image Effectively

For local images (in public or src):

import Image from 'next/image'
import heroBanner from '../public/images/hero-banner.jpg'

function HomePage() {
  return (
    <Image
      src={heroBanner}
      alt="Image description"
      width={1200}
      height={600}
      priority // Prioritize for LCP
      placeholder="blur" // Blur effect while loading
      // sizes helps the browser pick the right image for different screen sizes
      sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
    />
  )
}

For external images (CDN):

First, configure the image domain in next.config.js:

// next.config.js
module.exports = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'images.unsplash.com',
        port: '',
        pathname: '/**',
      },
    ],
  },
}

Then use it in your component:

import Image from 'next/image'

function PostCard({ post }) {
  return (
    <Image
      src={post.imageUrl} // "https://images.unsplash.com/photo-..."
      alt={post.title}
      width={500}
      height={300}
      loading="lazy" // Default, but can be explicit
    />
  )
}

💡 Pro Tip: Always provide accurate width and height. Use the sizes prop to optimize image selection for different devices and use priority for the most important image on the page.

2. next/font: Font Solution Without Layout Shift ✍️

Web fonts are beautiful, but they can cause performance issues like Flash of Invisible Text (FOIT) or Flash of Unstyled Text (FOUT), and worse, layout shift when the font loads and replaces the system font.

next/font is a revolution in font management. It automatically optimizes and self-hosts your fonts, eliminating these issues.

Outstanding Benefits of next/font

Why you should use next/font right away:

  • Automatic Self-hosting: Instead of fetching from Google Fonts on every page load, next/font downloads the font and serves it with your static assets. This removes a network round-trip, reducing render-blocking time.
  • Zero Layout Shift: This is the killer feature. next/font uses the CSS size-adjust property to make the fallback system font occupy the exact same space as the web font. The result? When the web font loads, there is no shift at all.
  • High Performance: next/font automatically adds font-display: optional; (or swap) to CSS, so the browser displays text immediately with the fallback font.

How to Use next/font

With Google Fonts:

// In your layout.js or _app.js
import { Inter, Roboto_Mono } from 'next/font/google'

// Initialize fonts with options
const inter = Inter({
  subsets: ['latin'],
  display: 'swap',
  variable: '--font-inter', // Use with CSS Variables
})

const robotoMono = Roboto_Mono({
  subsets: ['latin'],
  display: 'swap',
  variable: '--font-roboto-mono',
})

export default function RootLayout({ children }) {
  return (
    <html lang="en" className={`${inter.variable} ${robotoMono.variable}`}>
      <body>{children}</body>
    </html>
  )
}

Then in your globals.css:

body {
  font-family: var(--font-inter), sans-serif;
}

h1,
h2,
code {
  font-family: var(--font-roboto-mono), monospace;
}

With Local Fonts:

// In your layout.js or _app.js
import localFont from 'next/font/local'

const myFont = localFont({
  src: './MyCustomFont.woff2',
  display: 'swap',
  variable: '--font-my-custom',
})

export default function RootLayout({ children }) {
  return (
    <html lang="en" className={myFont.variable}>
      <body>{children}</body>
    </html>
  )
}

💡 Pro Tip: Always use next/font instead of the traditional <link> tag in _document.js for fonts. It brings performance and UX benefits with minimal configuration.

3. next/script: The "Conductor" for Third-Party Scripts 📜

Third-party scripts (analytics, chatbots, ads, Google Tag Manager) are among the top causes of slow websites and high Total Blocking Time (TBT). They often block the main thread, delaying user interaction.

The next/script component lets you precisely control when and how these scripts are loaded and executed.

Loading Strategies

next/script provides a powerful strategy prop:

  • strategy="beforeInteractive":
    • When to use: Only for scripts that are absolutely critical and must run before the page is interactive, such as cookie consent managers.
    • Note: Use with caution as it can block page rendering.
  • strategy="afterInteractive" (Default):
    • When to use: Perfect for most scripts. The script loads and runs right after the page becomes interactive (hydrated).
    • Example: Google Analytics, Google Tag Manager.
  • strategy="lazyOnload":
    • When to use: For low-priority scripts that can wait until all other resources are loaded and the browser is idle.
    • Example: Chatbots (Intercom, Crisp), social widgets.

How to Use next/script

import Script from 'next/script'

function MyPage() {
  return (
    <div>
      <h1>My Page</h1>
      {/* Google Tag Manager - Load after page is interactive */}
      <Script
        src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"
        strategy="afterInteractive"
      />
      <Script id="google-analytics" strategy="afterInteractive">
        {`
          window.dataLayer = window.dataLayer || [];
          function gtag(){dataLayer.push(arguments);}
          gtag('js', new Date());
          gtag('config', 'GA_MEASUREMENT_ID');
        `}
      </Script>

      {/* Intercom Chat Widget - Load when browser is idle */}
      <Script
        src="https://widget.intercom.io/widget/APP_ID"
        strategy="lazyOnload"
      />
    </div>
  )
}

Advanced Optimization with Web Workers (Partytown)

For heavy scripts, you can offload them from the main thread entirely by running them in a Web Worker. Next.js integrates with Partytown to make this easy.

To enable, in next.config.js:

// next.config.js
module.exports = {
  experimental: {
    workerThreads: true,
    cpus: 1, // Limit number of workers to save resources
  },
}

Then use strategy="worker":

<Script src="https://example.com/heavy-script.js" strategy="worker" />

This shifts the script's processing load to a background thread, keeping your UI smooth and responsive.

Conclusion: Optimization is an Ongoing Process

Optimization is not a one-time job, but a continuous process. However, by mastering and applying these three powerful tools that Next.js provides:

  • next/image: For fast, lightweight, layout-shift-free images.
  • next/font: For beautiful fonts without sacrificing performance or user experience.
  • next/script: For controlling third-party scripts and keeping the main thread clear.

You have the toolkit needed to build web apps that are not only beautiful and feature-rich but also blazingly fast. Start reviewing your project today and apply these techniques—you'll see a clear difference in tools like PageSpeed Insights, and more importantly, in your users' satisfaction.

Related Posts

[Next.js Tutorial] Basic Directory Structure and Routing in Your Project

How to organize your Next.js project effectively? A detailed guide on directory structure and basic routing, helping you build Next.js websites quickly and properly.

[Next.js Tutorial] Dynamic Routes: How to Use and Optimize

Learn how to create and manage Dynamic Routes in Next.js (App Router). This article provides a step-by-step guide to help you build dynamic, SEO-friendly web pages efficiently.

[Next.js Tutorial] Mastering Rendering Strategies: SSG, SSR, ISR, and CSR

Do you truly understand the rendering strategies in Next.js? This article dives deep into SSR, SSG, CSR, and ISR. Discover how they work to optimize your website’s performance.

[Next.js Tutorial] What is Next.js? Why Should You Choose It in 2025?

Curious about Next.js? This article explains what Next.js is, why it matters for SEO and performance, and the outstanding benefits of using it.