Add a timeout to next/font/google in dev (#46834)

Add a timeout to the Google Fonts fetch calls in dev. In case they
aren't fetched in time, the fallback font is used instead.

Currently if font fetching fails due to network errors in dev, the
loader is not cached. Every change on a page that uses a font makes it
retry to refetch the font. But, if the network is slow enough to cause
timeouts, that means that every change would force the user to wait for
the timeout before the page being updated. Because of this, the PR also
enables the loader to be cached on error. The drawback is that you would
have to delete the `.next` folder to retry to get the actual font in
case it got cached after failing.

Closes NEXT-726

## Bug

- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Errors have a helpful link attached, see
[`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md)

## Feature

- [ ] Implements an existing feature request or RFC. Make sure the
feature request has been accepted for implementation before opening a
PR.
- [ ] Related issues linked using `fixes #number`
- [ ]
[e2e](https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs)
tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have a helpful link attached, see
[`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md)

## Documentation / Examples

- [ ] Make sure the linting passes by running `pnpm build && pnpm lint`
- [ ] The "examples guidelines" are followed from [our contributing
doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md)
This commit is contained in:
Hannes Bornö 2023-03-07 13:57:43 +01:00 committed by GitHub
parent a88b0335b6
commit 14bfe87110
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 21 additions and 7 deletions

View file

@ -11,7 +11,8 @@ import { nextFontError } from '../next-font-error'
*/
export async function fetchCSSFromGoogleFonts(
url: string,
fontFamily: string
fontFamily: string,
isDev: boolean
): Promise<string> {
// Check if mocked responses are defined, if so use them instead of fetching from Google Fonts
let mockedResponse: string | undefined
@ -28,12 +29,18 @@ export async function fetchCSSFromGoogleFonts(
// Just use the mocked CSS if it's set
cssResponse = mockedResponse
} else {
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), 3000)
const res = await fetch(url, {
// Add a timeout in dev
signal: isDev ? controller.signal : undefined,
headers: {
// The file format is based off of the user agent, make sure woff2 files are fetched
'user-agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36',
},
}).finally(() => {
clearTimeout(timeoutId)
})
if (!res.ok) {

View file

@ -4,7 +4,7 @@ import fetch from 'next/dist/compiled/node-fetch'
/**
* Fetch the url and return a buffer with the font file.
*/
export async function fetchFontFile(url: string) {
export async function fetchFontFile(url: string, isDev: boolean) {
// Check if we're using mocked data
if (process.env.NEXT_FONT_GOOGLE_MOCKED_RESPONSES) {
// If it's an absolute path, read the file from the filesystem
@ -15,6 +15,15 @@ export async function fetchFontFile(url: string) {
return Buffer.from(url)
}
const arrayBuffer = await fetch(url).then((r: any) => r.arrayBuffer())
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), 3000)
const arrayBuffer = await fetch(url, {
// Add a timeout in dev
signal: isDev ? controller.signal : undefined,
})
.then((r: any) => r.arrayBuffer())
.finally(() => {
clearTimeout(timeoutId)
})
return Buffer.from(arrayBuffer)
}

View file

@ -32,7 +32,6 @@ const nextFontGoogleFontLoader: FontLoader = async ({
emitFontFile,
isDev,
isServer,
loaderContext,
}) => {
const {
fontFamily,
@ -83,7 +82,7 @@ const nextFontGoogleFontLoader: FontLoader = async ({
// Fetch CSS from Google Fonts or get it from the cache
let fontFaceDeclarations = hasCachedCSS
? cssCache.get(url)
: await fetchCSSFromGoogleFonts(url, fontFamily).catch(() => null)
: await fetchCSSFromGoogleFonts(url, fontFamily, isDev).catch(() => null)
if (!hasCachedCSS) {
cssCache.set(url, fontFaceDeclarations ?? null)
} else {
@ -109,7 +108,7 @@ const nextFontGoogleFontLoader: FontLoader = async ({
// Download the font file or get it from cache
const fontFileBuffer = hasCachedFont
? fontCache.get(googleFontFileUrl)
: await fetchFontFile(googleFontFileUrl).catch(() => null)
: await fetchFontFile(googleFontFileUrl, isDev).catch(() => null)
if (!hasCachedFont) {
fontCache.set(googleFontFileUrl, fontFileBuffer ?? null)
} else {
@ -157,7 +156,6 @@ const nextFontGoogleFontLoader: FontLoader = async ({
css: updatedCssResponse,
}
} catch (err) {
loaderContext.cacheable(false)
if (isDev) {
if (isServer) {
console.error(err)