Degraded Performance Caused by Next.js Webfont Optimization: A Case Study
Shinji NAKAMATSU
Posted on October 5, 2023
Photo by Charlie M on Unsplash
TL;DR
- A website built with Next.js experienced performance issues under certain conditions.
- The cause of the problem was a significant expansion of HTML size due to the Automatic Webfont Optimization feature.
- As a solution, we utilized
next/font
to optimize the method of loading Webfonts and reduced the HTML size.
Specific Circumstances of the Project
- Deploying and test-running the Next.js application on ECS
- Providing multilingual support (Japanese, Korean, Chinese (Traditional, Simplified))
- Originally started building with Next.js v12, and just last month, finally completed the update to v13
The Problem That Occurred
Our team had an opportunity to measure the performance of a particular website built with Next.js. Surprisingly, even though the number of requests was not so high, we noticed IPC (Inter-Process Communication) related errors occurring on the server side.
Another error also occurred:(Part of the occurring error)
TypeError: fetch failed
at Object.fetch (node:internal/deps/undici/undici:11457:11)
at async invokeRequest (/home/snaka/xxxxx/node_modules/next/dist/server/lib/server-ipc/invoke-request.js:17:12)
at async invokeRender (/home/snaka/xxxxx/node_modules/next/dist/server/lib/router-server.js:254:29)
at async handleRequest (/home/snaka/xxxxx/node_modules/next/dist/server/lib/router-server.js:447:24)
at async requestHandler (/home/snaka/xxxxx/node_modules/next/dist/server/lib/router-server.js:464:13)
at async Server.<anonymous> (/home/snaka/xxxxx/node_modules/next/dist/server/lib/start-server.js:117:13) {
cause: Error: read ECONNRESET
at TCP.onStreamRead (node:internal/stream_base_commons:217:20) {
errno: -104,
code: 'ECONNRESET',
syscall: 'read'
}
}
cause: SocketError: other side closed
at Socket.onSocketEnd (/app/node_modules/next/dist/compiled/undici/index.js:1:63301)
at Socket.emit (node:events:525:35)
at endReadableNT (node:internal/streams/readable:1359:12)
at process.processTicksAndRejections (node:internal/process/task_queues:82:21) {
code: 'UND_ERR_SOCKET',
socket: {
localAddress: '127.0.0.1',
localPort: 45850,
remoteAddress: undefined,
remotePort: undefined,
remoteFamily: undefined,
timeout: undefined,
bytesWritten: 2937,
bytesRead: 87753
}
}
Upon further investigation, it became clear that the HTML built by Next.js was abnormally large. Specifically, its size was about 2MB, which was significantly larger compared to the size of a typical webpage's HTML.
The Cause of the Problem
To resolve the performance issue occurring on our website, we began a series of investigations to pinpoint the cause. During load testing, it was revealed that network bandwidth was a clear bottleneck, and we were particularly concerned about the significant bandwidth consumption when downloading a single HTML file.
Further investigation into the HTML revealed that it contained a large number of font definitions (@font-face
rules). These definitions were not present in the original source code and were automatically inserted by some mechanism. Further research revealed that these font definitions originated from the Automatic Webfont Optimization feature introduced in Next.js v10.2.
Reference: Next.js 10.2 Release Notes
The problematic section was the following description in _document.tsx.
// _document.tsx
<Head>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@100;300;400;500;700;900&display=swap" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@100;300;400;500;700;900&display=swap" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@100;300;400;500;700;900&display=swap" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@100;300;400;500;700;900&display=swap" />
</Head>
Sample code is available here:
Size of the built HTML (about 2MB)
-rw-r--r--@ 1 snaka staff 2359534 Sep 29 09:48 ja.html
-rw-r--r--@ 1 snaka staff 2359533 Sep 29 09:48 ko.html
-rw-r--r--@ 1 snaka staff 2359540 Sep 29 09:48 zh-CN.html
-rw-r--r--@ 1 snaka staff 2359540 Sep 29 09:48 zh-TW.html
Solution
The solution was simple. We stopped loading Webfonts in _document.tsx
and switched to using next/font
, introduced in Next.js v13.
This change significantly reduced the size of the HTML and resolved the IPC error when loading the server. Additionally, by using next/font
, it became possible to self-host Webfonts and control caching periods, etc., through the CDN.
The code for using Webfonts became as follows:
// index.tsx
import { Noto_Sans_JP, Noto_Sans_KR, Noto_Sans_SC, Noto_Sans_TC } from 'next/font/google'
const notoSansJP = Noto_Sans_JP({
weight: ['100', '300', '400', '500', '700', '900'],
preload: false
})
const notoSansSC = Noto_Sans_SC({
weight: ['100', '300', '400', '500', '700', '900'],
preload: false
})
const notoSansTC = Noto_Sans_TC({
weight: ['100', '300', '400', '500', '700', '900'],
preload: false
})
const notoSansKR = Noto_Sans_KR({
weight: ['100', '300', '400', '500', '700', '900'],
preload: false
})
// Use the loaded webfont as className={notoSans_JP.className}, etc.
Sample code is available here:
Size of the built HTML (about 5KB)
-rw-r--r--@ 1 snaka staff 5176 Sep 29 14:19 ja.html
-rw-r--r--@ 1 snaka staff 5175 Sep 29 14:19 ko.html
-rw-r--r--@ 1 snaka staff 5182 Sep 29 14:19 zh-CN.html
-rw-r--r--@ 1 snaka staff 5182 Sep 29 14:19 zh-TW.html
In Choosing the Approach
In this case, we adopted a method of changing the Webfont loading method and using next/font
without preload. This decision was made as a result of evaluating the following technical trade-offs.
-
Problems Before Improvement
- Increased communication caused instability in inter-process communication within the container
- The allocated CPU and memory performance for the container could not be fully utilized, necessitating the parallel operation of numerous containers
-
Negative Impact from Changing to
next/font
- Using
next/font
without preload β A slight time lag occurred until the Webfont was applied
- Using
-
Consideration of Alternatives
- Change the deployment destination to a FaaS platform? β Judged as high-risk
- Personally, it was very interesting but...
-
Final Decision
- Utilizing
next/font
provided benefits of resolving communication bottlenecks and effectively utilizing container resources - Compared cost-performance and a slight UX degradation, and prioritized cost-performance
- By loading divided files, we anticipate achieving service stability efficiently by distributing server load using CDNs.
- Utilizing
Through this choice, while the client-side overhead increased due to the additional files to be loaded, the final performance evaluation (Lighthouse) showed a slight improvement. (Approximately 13% enhancement)
In Conclusion
In this article, we presented an example of a performance issue and its solution that occurred on a website built with Next.js. We confirmed that the Automatic Webfont Optimization feature could cause a significant expansion in HTML size in certain cases, which, in turn, could potentially trigger IPC errors on the server side.
As a solution, by utilizing next/font
, we optimized the method of loading Webfonts, reduced the HTML size, and were able to alleviate the bottleneck on the server. This also enabled us to self-host Webfonts and control caching.
The speed of evolution in front-end technology is staggering, with new technologies and optimization methods being provided daily. While they can enhance the performance of a website when used appropriately, they are not omnipotent and can cause unexpected problems in some cases. Therefore, thorough testing is indispensable when introducing new features.
We hope this article proves helpful to someone.
This article was proofread by ChatGPT.
Pages Referred to
Posted on October 5, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.