- Published on
Learning Next.js - "Optimizing Fonts and Images" Chapter 3
- Authors

- Name
- Brett Stockmeier
Introduction
This was a weird, hard to parse chapter for me. I guess I've never thought about post optimization before. And the added Tailwind only made it harder to get a handle of.
Goals of this chapter
- How to add custom fonts with next/font.
- How to add images with next/image.
- How fonts and images are optimized in Next.js.
Why Optimize Fonts
My experience up to this point has been largely focused on back end development. Perhaps that's why I couldn't really appreciate how good coding practices can affect performance on the front end. Apparently fetching and loading custom fonts (and images?) can be quite taxing. Or so this chapter claims. It feels like I'm still taking this on faith, but I have no real reason to doubt either.
Cumulative Layout Shift1 is a new term that relates to Google's metrics and how they evaluate website performance (which in turn apparently factors into the order that their search results are returned). It results to fonts in that for many unoptimized browsers, websites are initially loading page content in a default font to display information ASAP, and only later loading the specified font and swapping out the content. But not all fonts are equal in appearance: they can vary in size and width. Simply swapping one font for another can push other elements out of position and change the entire layout and feel of a web page.
Next.js automatically handles font opitimization through its next/font module, which purports to download fonts at build time and hosts them with other static assets. This way they are ready to be displayed on page load without the need to resort to default fonts and element shifting when you switch fonts.
I'm assuming this comes at the cost of your project being that much larger after build as your static assets are now also holding the font files. I'm curious as to whether these are considered to be comparatively large or small in size?
Adding a Primary Font
The example steps for adding/applying a primary font (using next/font) according to the tutorial:
- In the ui folder of the project (where everything pertaining to the front end is stored), create a new typescript file,
fonts.tsto track the fonts used throughout the application. This doesn't track where these fonts are being used, but if they are not imported here, they cannot be used elsewhere. - From within
fonts.ts, import the font you want (in the case of the example, "Inter"), and export it. When exporting it, it is important to specify a subset of the font that you want included. I'm curious as to why this is built to be specified on the export step, and not the import. I understand the idea comes from the idea that the average user will not need access to the entire character set of the font, but are you not still importing and storing the entire font regardless? - Apply the font to the desired elements through the
classNameproperty (as we've seen with Tailwind before)
In fonts.ts:
import { Inter } from 'next/font/google';
export const inter = Inter({ subsets: ['latin'] });
In layout.tsx:
import '@/app/ui/global.css';
import { inter } from '@/app/ui/fonts';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body className={`${inter.className} antialiased`}>{children}</body>
</html>
);
}
In the tutorial (as shown above), we are applying the font to the <body> tag in layout.tsx to see the font be applied to the entirety of the application.
(They also recommend using antialiased which "smoothes out" the font. It "adds a nice touch". I'm curious if there's ever a reason NOT to use antialiased?)
Practice: Adding a Secondary Font
A practice exercise! Takeaways for me: beyond adding a "global" font through the layout.tsx <body> element can be overridden by applying a different font "further down the stream" (I know there's probably a more elequent way to put this, but I can't recall it from my education).
Why Optimize Images?
Essentially, this section exists to introduce some of the frequent and specific problems associated with unoptimized images. You can include images in your application using the <html> tag, but this means that in order to ensure a wide range of support across a variety of devices, there is a lot of manual work you need to do to ensure compatibility and proper performance.
Rather than try to rephrase the specific issues given as examples, I'll [mostly] just copy and paste them here. Image optimization can:
- Ensure your image is responsive on different screen sizes.
- Specify image sizes for different devices. = Prevent layout shift as the images load.
- Lazy load images that are outside the user's viewport.
Image specialization is apparently a large and complex enough issue that can warrant specialization on its own. But for the purposes of learning next.js, the tutorial will have me relying on the next/image component and pre-written code.
I admit for my own purposes, I'm more concerned with back end functionality, but given the nature of my personal project, proper and attractive presentation will be a necessity. Hopefully next/imsage will do the job when the time comes.
Speaking of...
The <Image> Component
There are 6 points to this section.
- It's actually an extention of vanilla
<img> - It comes with automatic image optimization (hopefully I'll never have to stress test this)
- Prevents layout shift while images load automatically
- "Resizing images to avoid shipping large images to devices with a smaller viewport." (I'm not sure I understand this sentecne? If I had to guess, I think it means that the
<Image>component will automatically scale images down to match smaller screens?) - Lazy loads2 images by default
- "Serving images in modern formats, like WebP and AVIF, when the browser supports it." (I don't actually know these formats! Perhaps something to look into)
Adding the Desktop/Mobile Hero Images
These sections have me trying out the <Image> component for myself by adding code to the tutorial project. There are two images, one to be displayed on desktop devices, and another to be displayed on mobile devices.
<Image
src="/hero-desktop.png"
width={1000}
height={760}
className="hidden md:block"
alt="Screenshots of the dashboard project showing desktop version"
/>
Takeaways:
- Tailwind styling can be applied to the
<Image>component via theclassNameproperty as well - It's apparently good practice to supply the width and height properties of the Image component. This isn't to lock the image to a certain size apparently, but more to inform the application of the aspect ratio of the image should be maintained (I may need a refresher on the best ways to ascertain an image's dimensions; I remember struggling with that in the past and I haven't had to do it for a long time)
- I had to do some deeper investigation into this part: for the
classNamein the above example:- hidden - hidden from view (or rather, flat out removed from the DOM3)
- md - medium sized screens
- block - block layout (not "blocked from view")
This effectively means that the image will by default be hidden from view unless the screen viewing the application is conisidered "medium" in size. This does not seem intuitive to me. Do we even all agree on what consitutues a "medium" sized screen?4
Adding the code for the mobile "hero image" (what is a hero image, anyway? is that an official industry term? I've never heard it before...), and we get the following code for both images:
<Image
src="/hero-desktop.png"
width={1000}
height={760}
className="hidden md:block"
alt="Screenshots of the dashboard project showing desktop version"
/>
<Image
src="/hero-mobile.png"
width={560}
height={620}
className="block md:hidden"
alt="Screenshot of the dashboard project showing mobile version"
/>
It is interesting to me here that for the mobile image, we are going the exact opposite of the desktop image: it is rendered by default, but hidden only on medium (which I guess at this point can be thought of as shorthand for "desktop" and maybe laptop) screens.
I realize this is only a tutorial, but... this can't be good practice, can it? If you're going to conditionally render images, wouldn't you want the default to be hidden, and manually specify when to block it?
I'll have to see if I can confirm this elsewhere I guess...
Footnotes
The tutorial links this article in relation to this topic, detailing how performance metrics and affect SEO (Search Engine Optimization). I should blog about this in the future. ↩
Lazy Loading is a methodology where images are loaded at the last possible moment: only loading them just before they enter the "screen". If I recall correctly, this can make the initial page load faster, but erratic scrolling/behavior can slow down performance as the application tries to keep up with the user's behavior ↩
Document Object Model. It is like a map of the structure of an HTML page. An object removed from the DOM is for all intents and purposes not on the web page ↩
The Tailwind documentation has documentation on "Responsive Design". Other options incldue: sm, lg, xl, and 2xl. Interestingly, these sizes seem to be soley defined by the width of the screen ↩
