6. Optimizing Performance and SEO
In the competitive world of web development, a fast-loading and search-engine-friendly application isn’t just a luxury—it’s a necessity. Next.js is built with performance and SEO in mind, offering powerful features out of the box. This chapter will guide you through leveraging these features, focusing on image optimization, font optimization, and robust metadata management to ensure your applications are both blazing fast and highly discoverable.
6.1 Image Optimization with next/image
Images often account for the largest portion of a webpage’s size, significantly impacting load times. Next.js provides the next/image component, a powerful tool that automatically optimizes images for performance and an improved user experience.
Key Features of next/image:
- Automatic Image Optimization: Next.js automatically resizes, optimizes, and serves images in modern formats (like WebP or AVIF) when the browser supports them. This dramatically reduces file sizes without compromising visual quality.
- Lazy Loading by Default: Images outside the viewport are not loaded until they are scrolled into view, reducing initial page load time.
- Responsive Images: Automatically generates
srcsetattributes, serving different image sizes for various screen resolutions and device pixel ratios. - Prevention of Cumulative Layout Shift (CLS): Requires
widthandheightprops (or thefillprop) to reserve space, preventing layout shifts as images load. - External Image Sources: Supports optimization for images hosted on external domains with simple configuration.
- Image Placeholders: Provides a
placeholderprop to show a blurred placeholder while the image loads, enhancing perceived performance.
Basic Usage
To use next/image, simply import it and replace your standard <img> tags.
Example:
Let’s use next/image on our home page.
Open
src/app/page.tsx.Add an image from an external source (make sure to configure
next.config.mjsfor remote patterns if needed).// src/app/page.tsx import Link from 'next/link'; import CustomButton from './components/CustomButton'; import Image from 'next/image'; // Import the Image component export default function Home() { const handleButtonClick = (buttonName: string) => { alert(`${buttonName} button clicked!`); }; return ( <main className="min-h-screen flex flex-col items-center justify-center p-6 bg-gradient-to-br from-blue-50 to-indigo-100 text-gray-800"> <Image src="https://images.unsplash.com/photo-1695669748983-63b7e75f4d1c?q=80&w=2940&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="Modern desk setup with laptop and coffee" width={700} // Original width of the image height={400} // Original height of the image priority // Load this image with high priority (for LCP) className="rounded-lg shadow-xl mb-8" style={{ objectFit: 'cover' }} // Ensure image covers its area /> <h1 className="text-5xl font-extrabold text-blue-800 mb-4">Welcome to Next.js!</h1> <p className="text-xl text-gray-700 mb-8 max-w-2xl text-center"> This is the homepage of your Next.js application. <br /> Let's learn and build amazing things! </p> <div className="flex space-x-4 mb-12"> <Link href="/blog" className="text-blue-600 hover:text-blue-800 font-medium text-lg"> View Blog Posts </Link> <Link href="/products" className="text-blue-600 hover:text-blue-800 font-medium text-lg"> Explore Products </Link> <Link href="/users" className="text-blue-600 hover:text-blue-800 font-medium text-lg"> See Users </Link> </div> <div className="mt-12 pt-8 border-t border-dashed border-gray-300 text-center"> <h2 className="text-3xl font-bold text-gray-900 mb-6">Check out our custom buttons:</h2> <div className="flex justify-center gap-4"> <CustomButton onClick={() => handleButtonClick('Primary')} variant="primary"> Primary Action </CustomButton> <CustomButton onClick={() => handleButtonClick('Secondary')} variant="secondary" size="large"> Large Secondary Action </CustomButton> </div> </div> </main> ); }Configure
next.config.mjsfor remote images: If you’re using external images (like from Unsplash, as in the example), you must configureremotePatternsinnext.config.mjsfor security and optimization.// next.config.mjs /** @type {import('next').NextConfig} */ const nextConfig = { images: { remotePatterns: [ { protocol: 'https', hostname: 'images.unsplash.com', // Allow images from Unsplash port: '', pathname: '**', }, // Add other remote hosts here if needed ], }, }; export default nextConfig;You might need to restart your development server (
npm run dev) after modifyingnext.config.mjs.Save the files and visit
http://localhost:3000. Open your browser’s developer tools (Network tab) and observe the image. Next.js will likely have converted it to WebP (or AVIF) and generated multiplesrcsetentries, showing its optimization in action.
Important next/image Props:
src: The path to your image (local or remote URL).alt: Essential for accessibility and SEO. Describes the image content.widthandheight: Required for preventing CLS. Specifies the intrinsic dimensions.priority: Set totruefor images that are “above the fold” (visible on initial load) to prioritize loading and improve Largest Contentful Paint (LCP).fill: Whentrue, the image will fill its parent element (which must haveposition: relative,absolute, orfixed). Useful for fluid images where explicitwidth/heightmight be restrictive.sizes: Provides a comma-separated list of media conditions and image slot widths. Crucial for generating optimalsrcsetwhenfillis used or for more complex responsive layouts.
Exercises/Mini-Challenges (Image Optimization):
Add a
placeholderto your homepage image:- Set
placeholder="blur"and add ablurDataURL(you can generate a base64 blurhash or use a small, blurred version of the image for local static images). - For external images, you might need a library like
plaiceholder(which you’d install) or manually provide a very small base64 image representation for testing. For this exercise, you can setplaceholder="empty"to just see the effect of the space being reserved.
- Set
Optimize an image in the Blog Page:
- Find a suitable image (either local in
/publicor an external one) and add it tosrc/app/blog/page.tsxusingnext/image. - Ensure it has
width,height, andaltattributes. - If it’s an image that appears early on the page, consider adding
priority.
- Find a suitable image (either local in
6.2 Font Optimization with next/font
Web fonts can also significantly impact performance, causing layout shifts (Flash of Unstyled Text - FOUT, or Flash of Invisible Text - FOIT). Next.js’s next/font module automatically optimizes fonts, eliminating external network requests and ensuring efficient loading.
Key Features of next/font:
- Automatic Self-Hosting: Downloads font files at build time and serves them from your domain. No requests are sent to Google (or other providers) by the browser, improving privacy and performance.
- Zero Layout Shift: Uses CSS
size-adjustto maintain the layout during font loading, preventing CLS. - Optimized Loading: Reduces font file sizes through subsetting (only includes characters needed) and uses modern font formats (WOFF2).
- Google Fonts & Local Fonts Support: Works seamlessly with both popular Google Fonts and your self-hosted local font files.
Using Google Fonts with next/font/google
Open your root
layout.tsxfile (src/app/layout.tsx).Import the desired font from
next/font/google. We are already usingInter, but let’s addRoboto_Monofor code blocks.// src/app/layout.tsx import type { Metadata } from "next"; import { Inter, Roboto_Mono } from "next/font/google"; // Import Roboto_Mono import "./globals.css"; const inter = Inter({ subsets: ["latin"], variable: '--font-inter' }); // Add variable const roboto_mono = Roboto_Mono({ subsets: ["latin"], variable: '--font-roboto-mono' }); // Define for code export const metadata: Metadata = { title: "My Next.js App", description: "Learning Next.js styling", }; export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( // Apply the font variables to the html element <html lang="en" className={`${inter.variable} ${roboto_mono.variable}`}> <body className={inter.className}>{children}</body> {/* Inter is the default body font */} </html> ); }Configure Tailwind CSS to use the font variable: (If you are using Tailwind)
- Open
tailwind.config.ts. - Update the
fontFamilyextension.
// tailwind.config.ts import type { Config } from 'tailwindcss'; const config: Config = { content: [ './pages/**/*.{js,ts,jsx,tsx,mdx}', './components/**/*.{js,ts,jsx,tsx,mdx}', './app/**/*.{js,ts,jsx,tsx,mdx}', './src/**/*.{js,ts,jsx,tsx,mdx}', ], theme: { extend: { backgroundImage: { 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', }, fontFamily: { // Add font family configuration sans: ['var(--font-inter)', 'sans-serif'], mono: ['var(--font-roboto-mono)', 'monospace'], }, }, }, plugins: [], }; export default config;- Open
Apply the
font-monoclass to elements where you want to use the monospace font (e.g.,<pre>or<code>tags).- Let’s add a code example to
src/app/page.tsxthat uses thefont-monoclass:
// src/app/page.tsx (inside the <main> element, perhaps near the bottom) // ... existing JSX ... <div className="mt-12 pt-8 border-t border-dashed border-gray-300 text-center"> <h2 className="text-3xl font-bold text-gray-900 mb-6">Check out our custom buttons:</h2> <div className="flex justify-center gap-4"> <CustomButton onClick={() => handleButtonClick('Primary')} variant="primary"> Primary Action </CustomButton> <CustomButton onClick={() => handleButtonClick('Secondary')} variant="secondary" size="large"> Large Secondary Action </CustomButton> </div> </div> <div className="mt-12 pt-8 border-t border-dashed border-gray-300"> <h2 className="text-2xl font-bold text-gray-900 mb-4">Code Example:</h2> <pre className="bg-gray-800 text-white p-4 rounded-md overflow-x-auto font-mono text-left"> {`// Example of using Roboto Mono for code- Let’s add a code example to
function greet(name: string) { console.log(`Hello, ${name}!`); }
greet(“World”); `}