From 0b1aca010b0c675876607fb8cee233e5679f1f52 Mon Sep 17 00:00:00 2001 From: Gabriel Horacio Cutrini Date: Wed, 27 May 2026 07:22:06 -0300 Subject: [PATCH 1/5] gatsby-node.js: throw API errors with context instead of swallowing them Build-time SSR fetchers were catching errors with console.log and returning undefined, causing downstream code to crash with confusing TypeError on .length. Replaces the swallow pattern with a SSR_handleError helper that throws an Error including HTTP status, URL, and the original axios error as cause. --- gatsby-node.js | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/gatsby-node.js b/gatsby-node.js index 110e53c9..a28226aa 100644 --- a/gatsby-node.js +++ b/gatsby-node.js @@ -76,6 +76,14 @@ const SSR_GetRemainingPages = async (endpoint, params, lastPage) => { return remainingPages.sort((a, b,) => a.current_page - b.current_page).map(p => p.data).flat(); } +const SSR_handleError = (e) => { + const status = e?.response?.status; + const statusText = e?.response?.statusText; + const url = e?.config?.url; + const detail = status ? `HTTP ${status} ${statusText || ""}`.trim() : (e?.message || String(e)); + throw new Error(`Build API request failed: ${detail}${url ? ` (url: ${url})` : ""}`, { cause: e }); +}; + const SSR_getMarketingSettings = async (baseUrl, summitId) => { const endpoint = `${baseUrl}/api/public/v1/config-values/all/shows/${summitId}`; @@ -93,7 +101,7 @@ const SSR_getMarketingSettings = async (baseUrl, summitId) => { return [...data.data, ...remainingPages]; - }).catch(e => console.log("ERROR: ", e)); + }).catch(SSR_handleError); }; const SSR_getEvents = async (baseUrl, summitId, accessToken) => { @@ -116,7 +124,7 @@ const SSR_getEvents = async (baseUrl, summitId, accessToken) => { return [...data.data, ...remainingPages]; - }).catch(e => console.log("ERROR: ", e)); + }).catch(SSR_handleError); }; const SSR_getSponsors = async (baseUrl, summitId, accessToken) => { @@ -139,7 +147,7 @@ const SSR_getSponsors = async (baseUrl, summitId, accessToken) => { return [...data.data, ...remainingPages]; - }).catch(e => console.log('ERROR: ', e)); + }).catch(SSR_handleError); }; const SSR_getSponsorCollections = async (allSponsors, baseUrl, summitId, accessToken) => { @@ -154,7 +162,7 @@ const SSR_getSponsorCollections = async (allSponsors, baseUrl, summitId, accessT console.log(`SSR_getSponsorCollection then data.current_page ${data.current_page} data.last_page ${data.last_page} total ${data.total}`) let remainingPages = await SSR_GetRemainingPages(endpoint, params, data.last_page); return [...data.data, ...remainingPages]; - }).catch(e => console.log('ERROR: ', e)); + }).catch(SSR_handleError); const sponsorsWithCollections = await Promise.all(allSponsors.map(async (sponsor) => { console.log(`Collections for ${sponsor.company.name}...`); @@ -187,7 +195,7 @@ const SSR_getSpeakers = async (baseUrl, summitId, accessToken, filter = null) => return [...data.data, ...remainingPages]; }) - .catch(e => console.log("ERROR: ", e)); + .catch(SSR_handleError); }; const SSR_getSummit = async (baseUrl, summitId, accessToken) => { @@ -203,7 +211,7 @@ const SSR_getSummit = async (baseUrl, summitId, accessToken) => { apiUrlWithParams ) .then(({ data }) => data) - .catch(e => console.log("ERROR: ", e)); + .catch(SSR_handleError); }; const SSR_getVoteablePresentations = async (baseUrl, summitId, accessToken) => { @@ -227,7 +235,7 @@ const SSR_getVoteablePresentations = async (baseUrl, summitId, accessToken) => { return [...data.data, ...remainingPages]; }) - .catch(e => console.log("ERROR: ", e)); + .catch(SSR_handleError); }; exports.onPreBootstrap = async () => { From 013c476bbc23211ddd8b30f6ac6d369ca148c2fe Mon Sep 17 00:00:00 2001 From: Gabriel Horacio Cutrini Date: Wed, 27 May 2026 07:24:54 -0300 Subject: [PATCH 2/5] gatsby-node.js: set 30s default axios timeout for build API requests Prevents a hung upstream connection from stalling the Netlify build. --- gatsby-node.js | 4 +++- src/utils/build-json/constants.js | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/gatsby-node.js b/gatsby-node.js index a28226aa..c07c21f6 100644 --- a/gatsby-node.js +++ b/gatsby-node.js @@ -41,9 +41,11 @@ const { generateColorsScssFile } = require("./src/utils/scssUtils"); -const { FIFTY_PER_PAGE } = require("./src/utils/build-json/constants"); +const { FIFTY_PER_PAGE, BUILD_REQUEST_TIMEOUT_MS } = require("./src/utils/build-json/constants"); const SpeakersAPIRequest = require("./src/utils/build-json/SpeakersAPIRequest"); +axios.defaults.timeout = BUILD_REQUEST_TIMEOUT_MS; + const fileBuildTimes = []; const getAccessToken = async (config, scope) => { diff --git a/src/utils/build-json/constants.js b/src/utils/build-json/constants.js index 2e9cfb4e..d54dcd13 100644 --- a/src/utils/build-json/constants.js +++ b/src/utils/build-json/constants.js @@ -1,5 +1,8 @@ const FIFTY_PER_PAGE = "50"; +const BUILD_REQUEST_TIMEOUT_MS = 30000; + module.exports = { - FIFTY_PER_PAGE + FIFTY_PER_PAGE, + BUILD_REQUEST_TIMEOUT_MS }; \ No newline at end of file From d95aef9d3611770a9bc5aad8a0b7f9796d605d66 Mon Sep 17 00:00:00 2001 From: Gabriel Horacio Cutrini Date: Wed, 27 May 2026 07:25:28 -0300 Subject: [PATCH 3/5] gatsby-node.js: throw on access token failure Previously a token-fetch failure was logged and the function returned undefined, causing every downstream SSR_get* call to fail with a misleading 401 instead of the actual auth issue. --- gatsby-node.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gatsby-node.js b/gatsby-node.js index c07c21f6..ff3d0156 100644 --- a/gatsby-node.js +++ b/gatsby-node.js @@ -54,7 +54,7 @@ const getAccessToken = async (config, scope) => { try { return await client.getToken({ scope }); } catch (error) { - console.log("Access Token error", error); + throw new Error(`Failed to obtain build access token: ${error?.message || error}`, { cause: error }); } }; From c49958437ebc982676f6feb62a155c8b05b05873 Mon Sep 17 00:00:00 2001 From: Gabriel Horacio Cutrini Date: Wed, 27 May 2026 07:27:32 -0300 Subject: [PATCH 4/5] gatsby-node.js: retry transient 5xx and network errors on build API requests Adds getWithRetry helper with exponential backoff that retries on 502/503/504 or network errors. Wired into all build-time API fetches so a single transient hiccup no longer fails the entire build. --- gatsby-node.js | 17 ++++++++------- src/utils/build-json/constants.js | 6 +++++- src/utils/build-json/getWithRetry.js | 32 ++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 9 deletions(-) create mode 100644 src/utils/build-json/getWithRetry.js diff --git a/gatsby-node.js b/gatsby-node.js index ff3d0156..aac936df 100644 --- a/gatsby-node.js +++ b/gatsby-node.js @@ -43,6 +43,7 @@ const { const { FIFTY_PER_PAGE, BUILD_REQUEST_TIMEOUT_MS } = require("./src/utils/build-json/constants"); const SpeakersAPIRequest = require("./src/utils/build-json/SpeakersAPIRequest"); +const getWithRetry = require("./src/utils/build-json/getWithRetry"); axios.defaults.timeout = BUILD_REQUEST_TIMEOUT_MS; @@ -66,7 +67,7 @@ const SSR_GetRemainingPages = async (endpoint, params, lastPage) => { } let remainingPages = await Promise.all(pages.map(pageIdx => { - return axios.get(endpoint, + return getWithRetry(endpoint, { params: { ...params, @@ -95,7 +96,7 @@ const SSR_getMarketingSettings = async (baseUrl, summitId) => { page: 1 }; - return await axios.get(endpoint, { params }).then(async ({ data }) => { + return await getWithRetry(endpoint, { params }).then(async ({ data }) => { console.log(`SSR_getMarketingSettings then data.current_page ${data.current_page} data.last_page ${data.last_page} total ${data.total}`) @@ -118,7 +119,7 @@ const SSR_getEvents = async (baseUrl, summitId, accessToken) => { const params = EventAPIRequest.getParams(apiUrl); - return await axios.get(apiUrlWithParams).then(async ({ data }) => { + return await getWithRetry(apiUrlWithParams).then(async ({ data }) => { console.log(`SSR_getEvents then data.current_page ${data.current_page} data.last_page ${data.last_page} total ${data.total}`) @@ -141,7 +142,7 @@ const SSR_getSponsors = async (baseUrl, summitId, accessToken) => { expand: 'company,sponsorship,sponsorship.type', } - return await axios.get(endpoint, { params }).then(async ({ data }) => { + return await getWithRetry(endpoint, { params }).then(async ({ data }) => { console.log(`SSR_getSponsors then data.current_page ${data.current_page} data.last_page ${data.last_page} total ${data.total}`) @@ -160,7 +161,7 @@ const SSR_getSponsorCollections = async (allSponsors, baseUrl, summitId, accessT page: 1, } - const getSponsorCollection = async (endpoint, params) => await axios.get(endpoint, { params }).then(async ({ data }) => { + const getSponsorCollection = async (endpoint, params) => await getWithRetry(endpoint, { params }).then(async ({ data }) => { console.log(`SSR_getSponsorCollection then data.current_page ${data.current_page} data.last_page ${data.last_page} total ${data.total}`) let remainingPages = await SSR_GetRemainingPages(endpoint, params, data.last_page); return [...data.data, ...remainingPages]; @@ -189,7 +190,7 @@ const SSR_getSpeakers = async (baseUrl, summitId, accessToken, filter = null) => const params = SpeakersAPIRequest.getParams(apiUrl); - return await axios.get(apiUrlWithParams) + return await getWithRetry(apiUrlWithParams) .then(async ({ data }) => { console.log(`SSR_getSpeakers then data.current_page ${data.current_page} data.last_page ${data.last_page} total ${data.total}`) @@ -209,7 +210,7 @@ const SSR_getSummit = async (baseUrl, summitId, accessToken) => { const apiUrlWithParams = SummitAPIRequest.build(apiUrl); - return await axios.get( + return await getWithRetry( apiUrlWithParams ) .then(({ data }) => data) @@ -228,7 +229,7 @@ const SSR_getVoteablePresentations = async (baseUrl, summitId, accessToken) => { expand: "slides,links,videos,media_uploads,type,track,track.allowed_access_levels,location,location.venue,location.floor,speakers,moderator,sponsors,current_attendance,groups,rsvp_template,tags", }; - return await axios.get(endpoint, + return await getWithRetry(endpoint, { params }).then(async ({ data }) => { console.log(`SSR_getVoteablePresentations then data.current_page ${data.current_page} data.last_page ${data.last_page} total ${data.total}`) diff --git a/src/utils/build-json/constants.js b/src/utils/build-json/constants.js index d54dcd13..8b022804 100644 --- a/src/utils/build-json/constants.js +++ b/src/utils/build-json/constants.js @@ -1,8 +1,12 @@ const FIFTY_PER_PAGE = "50"; const BUILD_REQUEST_TIMEOUT_MS = 30000; +const BUILD_REQUEST_MAX_RETRIES = 2; +const BUILD_REQUEST_RETRY_BASE_BACKOFF_MS = 500; module.exports = { FIFTY_PER_PAGE, - BUILD_REQUEST_TIMEOUT_MS + BUILD_REQUEST_TIMEOUT_MS, + BUILD_REQUEST_MAX_RETRIES, + BUILD_REQUEST_RETRY_BASE_BACKOFF_MS }; \ No newline at end of file diff --git a/src/utils/build-json/getWithRetry.js b/src/utils/build-json/getWithRetry.js new file mode 100644 index 00000000..154ce585 --- /dev/null +++ b/src/utils/build-json/getWithRetry.js @@ -0,0 +1,32 @@ +const axios = require("axios"); +const { + BUILD_REQUEST_MAX_RETRIES, + BUILD_REQUEST_RETRY_BASE_BACKOFF_MS +} = require("./constants"); + +const RETRIABLE_STATUSES = new Set([502, 503, 504]); + +const isRetriable = (error) => { + if (!error?.response) return true; + return RETRIABLE_STATUSES.has(error.response.status); +}; + +const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); + +const getWithRetry = async ( + url, + options = {}, + retriesLeft = BUILD_REQUEST_MAX_RETRIES, + backoffMs = BUILD_REQUEST_RETRY_BASE_BACKOFF_MS +) => { + try { + return await axios.get(url, options); + } catch (error) { + if (retriesLeft <= 0 || !isRetriable(error)) throw error; + console.log(`getWithRetry: ${error?.response?.status || error?.code || error?.message} on ${url}, retrying in ${backoffMs}ms (${retriesLeft} left)`); + await delay(backoffMs); + return getWithRetry(url, options, retriesLeft - 1, backoffMs * 2); + } +}; + +module.exports = getWithRetry; From 4c9d2e5ecbe1aa8054e8463b3315d4a51d553054 Mon Sep 17 00:00:00 2001 From: Gabriel Horacio Cutrini Date: Wed, 27 May 2026 07:28:29 -0300 Subject: [PATCH 5/5] gatsby-node.js: cap parallel build page fetches at 5 Pagination remainder fetches were fanned out unbounded via Promise.all. For high-volume summits this caused 25+ concurrent requests per endpoint, raising the probability of upstream timeouts. Now processed in chunks of 5. --- gatsby-node.js | 21 ++++++++++----------- src/utils/build-json/constants.js | 4 +++- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/gatsby-node.js b/gatsby-node.js index aac936df..259b5474 100644 --- a/gatsby-node.js +++ b/gatsby-node.js @@ -41,7 +41,7 @@ const { generateColorsScssFile } = require("./src/utils/scssUtils"); -const { FIFTY_PER_PAGE, BUILD_REQUEST_TIMEOUT_MS } = require("./src/utils/build-json/constants"); +const { FIFTY_PER_PAGE, BUILD_REQUEST_TIMEOUT_MS, BUILD_PAGE_FETCH_CONCURRENCY } = require("./src/utils/build-json/constants"); const SpeakersAPIRequest = require("./src/utils/build-json/SpeakersAPIRequest"); const getWithRetry = require("./src/utils/build-json/getWithRetry"); @@ -60,21 +60,20 @@ const getAccessToken = async (config, scope) => { }; const SSR_GetRemainingPages = async (endpoint, params, lastPage) => { - // create an array with remaining pages to perform Promise.All const pages = []; for (let i = 2; i <= lastPage; i++) { pages.push(i); } - let remainingPages = await Promise.all(pages.map(pageIdx => { - return getWithRetry(endpoint, - { - params: { - ...params, - page: pageIdx - } - }).then(({ data }) => data); - })); + const fetchPage = (pageIdx) => + getWithRetry(endpoint, { params: { ...params, page: pageIdx } }).then(({ data }) => data); + + const remainingPages = []; + for (let i = 0; i < pages.length; i += BUILD_PAGE_FETCH_CONCURRENCY) { + const chunk = pages.slice(i, i + BUILD_PAGE_FETCH_CONCURRENCY); + const chunkResults = await Promise.all(chunk.map(fetchPage)); + remainingPages.push(...chunkResults); + } return remainingPages.sort((a, b,) => a.current_page - b.current_page).map(p => p.data).flat(); } diff --git a/src/utils/build-json/constants.js b/src/utils/build-json/constants.js index 8b022804..ba2c6af4 100644 --- a/src/utils/build-json/constants.js +++ b/src/utils/build-json/constants.js @@ -3,10 +3,12 @@ const FIFTY_PER_PAGE = "50"; const BUILD_REQUEST_TIMEOUT_MS = 30000; const BUILD_REQUEST_MAX_RETRIES = 2; const BUILD_REQUEST_RETRY_BASE_BACKOFF_MS = 500; +const BUILD_PAGE_FETCH_CONCURRENCY = 5; module.exports = { FIFTY_PER_PAGE, BUILD_REQUEST_TIMEOUT_MS, BUILD_REQUEST_MAX_RETRIES, - BUILD_REQUEST_RETRY_BASE_BACKOFF_MS + BUILD_REQUEST_RETRY_BASE_BACKOFF_MS, + BUILD_PAGE_FETCH_CONCURRENCY }; \ No newline at end of file