Skip to content

Commit 702d3d2

Browse files
authored
Add retries and clear message to font fetching (#51890)
Improve the problems mentioned in #45080. - Adding error retries (3 attempts) to font fetching. When you have a slow network, it might take >3s to load. - Improve the error message to tell you that you might have unstable network connection. - Do not cause build error (caused by missing `AbortController`) in Node 14 (even if we don't support Node 14, better to pass the build instead of hard error).
1 parent 2c856cb commit 702d3d2

File tree

1 file changed

+48
-18
lines changed

1 file changed

+48
-18
lines changed

packages/font/src/google/fetch-css-from-google-fonts.ts

Lines changed: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,22 @@ import fetch from 'next/dist/compiled/node-fetch'
33
import { nextFontError } from '../next-font-error'
44
import { getProxyAgent } from './get-proxy-agent'
55

6+
async function retry<T>(fn: () => Promise<T>, attempts: number): Promise<T> {
7+
let cnt = attempts
8+
while (true) {
9+
try {
10+
return await fn()
11+
} catch (err) {
12+
cnt--
13+
if (cnt <= 0) throw err
14+
console.error(
15+
(err as Error).message + `\n\nRetrying ${attempts - cnt}/3...`
16+
)
17+
await new Promise((resolve) => setTimeout(resolve, 100))
18+
}
19+
}
20+
}
21+
622
/**
723
* Fetches the CSS containing the @font-face declarations from Google Fonts.
824
* The fetch has a user agent header with a modern browser to ensure we'll get .woff2 files.
@@ -30,26 +46,40 @@ export async function fetchCSSFromGoogleFonts(
3046
// Just use the mocked CSS if it's set
3147
cssResponse = mockedResponse
3248
} else {
33-
const controller = new AbortController()
34-
const timeoutId = setTimeout(() => controller.abort(), 3000)
35-
const res = await fetch(url, {
36-
agent: getProxyAgent(),
37-
// Add a timeout in dev
38-
signal: isDev ? controller.signal : undefined,
39-
headers: {
40-
// The file format is based off of the user agent, make sure woff2 files are fetched
41-
'user-agent':
42-
'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',
43-
},
44-
}).finally(() => {
45-
clearTimeout(timeoutId)
46-
})
49+
// Retry the fetch a few times in case of network issues as some font files
50+
// are quite large:
51+
// https://github.com/vercel/next.js/issues/45080
52+
cssResponse = await retry(async () => {
53+
const controller =
54+
isDev && typeof AbortController !== 'undefined'
55+
? new AbortController()
56+
: undefined
57+
const signal = controller?.signal
58+
const timeoutId = controller
59+
? setTimeout(() => controller.abort(), 3000)
60+
: undefined
4761

48-
if (!res.ok) {
49-
nextFontError(`Failed to fetch font \`${fontFamily}\`.\nURL: ${url}`)
50-
}
62+
const res = await fetch(url, {
63+
agent: getProxyAgent(),
64+
// Add a timeout in dev
65+
signal,
66+
headers: {
67+
// The file format is based off of the user agent, make sure woff2 files are fetched
68+
'user-agent':
69+
'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',
70+
},
71+
}).finally(() => {
72+
timeoutId && clearTimeout(timeoutId)
73+
})
74+
75+
if (!res.ok) {
76+
nextFontError(
77+
`Failed to fetch font \`${fontFamily}\`.\nURL: ${url}\n\nPlease check if the network is available.`
78+
)
79+
}
5180

52-
cssResponse = await res.text()
81+
return res.text()
82+
}, 3)
5383
}
5484

5585
return cssResponse

0 commit comments

Comments
 (0)