A very frustrating problem for me today. With a very easy fix, I’m sure you will be glad to hear. The problem I was hitting is when running a build with NextJS, I was consistently getting a set number of pages fail to render with the error: Error occurred prerendering page.
All of my pages are being rendered ahead of time, as the query that builds each page can be fairly chunky. I knew this was likely the cause of the problem – but the queries themselves, whilst slow, do always resolve / give a response.
These queries are coming from a GraphQL API – but I don’t think that matters.
The issue, it turns out, is the use of Node’s native fetch
.
See, I didn’t want to use a third party library for making HTTP requests when fetch
is now a native feature of Node. And I am using Node 19, so why add yet another dependency, right?
Well. That is the fix.
But first, here’s what I would see when running the build:
api error {
e: TypeError: fetch failed
at Object.fetch (node:internal/deps/undici/undici:11404:11)
at async fetchAPI (/app/.next/server/pages/[sector]/[job-role].js:2019:21)
at async Promise.all (index 1)
at async jobRoleStaticPropHelper (/app/.next/server/pages/[sector]/[job-role].js:2417:256)
at async getStaticProps (/app/.next/server/pages/[sector]/[job-role].js:2563:59)
at async renderToHTML (/app/node_modules/next/dist/server/render.js:385:20)
at async /app/node_modules/next/dist/export/worker.js:286:36
at async Span.traceAsyncFn (/app/node_modules/next/dist/trace/trace.js:79:20) {
cause: HeadersTimeoutError: Headers Timeout Error
at Timeout.onParserTimeout [as callback] (node:internal/deps/undici/undici:9596:32)
at Timeout.onTimeout [as _onTimeout] (node:internal/deps/undici/undici:7899:17)
at listOnTimeout (node:internal/timers:569:17)
at process.processTimers (node:internal/timers:512:7) {
code: 'UND_ERR_HEADERS_TIMEOUT'
}
}
}
Error occurred prerendering page "/accounting/accounting". Read more: https://nextjs.org/docs/messages/prerender-error
TypeError: fetch failed
at Object.fetch (node:internal/deps/undici/undici:11404:11)
at async fetchAPI (/app/.next/server/pages/[sector]/[job-role].js:2019:21)
at async Promise.all (index 1)
at async jobRoleStaticPropHelper (/app/.next/server/pages/[sector]/[job-role].js:2417:256)
at async getStaticProps (/app/.next/server/pages/[sector]/[job-role].js:2563:59)
at async renderToHTML (/app/node_modules/next/dist/server/render.js:385:20)
at async /app/node_modules/next/dist/export/worker.js:286:36
at async Span.traceAsyncFn (/app/node_modules/next/dist/trace/trace.js:79:20)
info - Generating static pages (38/38)
> Build error occurred
Error: Export encountered errors on following paths:
/[sector]/[job-role]: /accounting/accounting
at /app/node_modules/next/dist/export/index.js:415:19
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async Span.traceAsyncFn (/app/node_modules/next/dist/trace/trace.js:79:20)
at async /app/node_modules/next/dist/build/index.js:1400:21
at async Span.traceAsyncFn (/app/node_modules/next/dist/trace/trace.js:79:20)
at async /app/node_modules/next/dist/build/index.js:1259:17
at async Span.traceAsyncFn (/app/node_modules/next/dist/trace/trace.js:79:20)
at async Object.build [as default] (/app/node_modules/next/dist/build/index.js:66:29)
Code language: JavaScript (javascript)
That’s restricted down to one page I found that always failed.
And I could get a sense of why it was failing when I looked at the Docker log output:
jobsboard-listing-graphql | 0 error(s) in 42103.31ms :: ...
Yes, 42 seconds to resolve the query.
Hence why I want to run this ahead of time, so visitors to the site don’t think I’ve lost my mind.
For some context, what’s happening here is the results are being pulled out to make this block:
The figures are wrong.
Badly wrong.
I mean it would be super nice if, on average, we earned £127,000 a year, right? Maybe in America, I guess that’s normal?
But that’s not the issue here. That’s a result of blindly trusting web scraping data filled in by chimps.
The actual NextJS code for this is really standard getStaticPaths
/ getStaticProps
stuff.
Each of the pages I want to pre-render ultimately make an API call using this function:
export async function fetchAPI(
query: string,
variables: { [key: string]: string | number | boolean } = {}
) {
try {
const url = `${process.env.GRAPHQL_BASE_URL}/graphql`;
const res = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
query,
variables,
}),
});
const json = await res.json();
if (json.errors) {
console.log(`url`, {
url,
graphql: {
query,
variables,
},
});
console.error(json.errors);
throw new Error("Failed to fetch API");
}
return json.data;
} catch (e) {
console.error("api error", { e });
throw e;
}
}
Code language: TypeScript (typescript)
Note there the use of fetch
.
No extra dependencies here.
And as far as I can see, by default Node’s fetch
does not have a timeout.
But everything I can see indicates this function is where the problem lies. I’ve tried many things to set a timeout, or somehow increase any default timeouts, but have had no luck.
The fix was to use axios:
import axios from "axios";
export async function fetchAPI(
query: string,
variables: { [key: string]: string | number | boolean } = {}
) {
try {
const url = `${process.env.GRAPHQL_BASE_URL}/graphql`;
const res = await axios.post(url, {
query,
variables,
});
const json = await res.data;
if (json.errors) {
console.log(`url`, {
url,
graphql: {
query,
variables,
},
});
console.error(json.errors);
throw new Error("Failed to fetch API");
}
return json.data;
} catch (e) {
console.error("api error", { e });
throw e;
}
}
Code language: TypeScript (typescript)
It’s actually pretty annoying to not understand why the fetch
call times out. There is surely a way to control it, but I have no idea how.
If you know, leave a comment.
Otherwise, I have to get back to fixing more bugs.