From f0aec3e2db9bec13dbf151a826ca35502ea33d4a Mon Sep 17 00:00:00 2001 From: anyulled <100741+anyulled@users.noreply.github.com> Date: Tue, 2 Jun 2026 08:47:32 +0000 Subject: [PATCH 1/2] feat: [performance improvement] Parallelize generateStaticParams fetching Replaced sequential for...of loops inside generateStaticParams with Promise.all mapping to fetch data concurrently. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- .jules/bolt.md | 4 +++ app/[year]/speakers/[speakerId]/page.tsx | 22 ++++++------- app/[year]/tags/[tag]/page.tsx | 39 +++++++++++++----------- app/[year]/talks/[talkId]/page.tsx | 26 ++++++++-------- 4 files changed, 49 insertions(+), 42 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index 9345ee7a..24587657 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -2,3 +2,7 @@ **Learning:** In Next.js/React applications, when grouping items (like schedules or talks) into a `Map` where the values are arrays, using the array spread operator `[...existing, item]` inside a loop (like `forEach` or `map`) causes amortized O(N^2) memory allocations and unnecessary Garbage Collection overhead. **Action:** Always use `.push()` on the existing array reference if the data structure permits local mutation. For strict ESLint configurations enforcing `no-restricted-syntax`, extract the existing array, push to it, and handle the fallback elegantly (`if (!existing) { map.set(key, [item]); } else { existing.push(item); }`). +## 2024-05-19 - Parallelize generateStaticParams fetching + +**Learning:** In Next.js applications, sequential data fetching (using `for...of` loops) inside build-critical functions like `generateStaticParams` significantly slows down static page generation. The bottleneck is amplified when fetching data across multiple parameters (e.g., archived years). +**Action:** Replace sequential `for...of` loops with parallel execution using `Promise.all(array.map(...))` to fetch data concurrently. Preserve per-item `try/catch` blocks inside the map callback to prevent a single item's failure from prematurely failing the entire batch, returning an empty array `[]` as a fallback, and then use `.flat()` on the resulting nested array structure. diff --git a/app/[year]/speakers/[speakerId]/page.tsx b/app/[year]/speakers/[speakerId]/page.tsx index 62b234bd..e45658e2 100644 --- a/app/[year]/speakers/[speakerId]/page.tsx +++ b/app/[year]/speakers/[speakerId]/page.tsx @@ -20,20 +20,20 @@ interface SpeakerDetailProps { export async function generateStaticParams() { const years = getArchivedEditions(); - const params = []; - for (const year of years) { - try { - const speakers = await getSpeakers(year); - for (const speaker of speakers) { - params.push({ year, speakerId: speaker.id }); + const paramsArrays = await Promise.all( + years.map(async (year) => { + try { + const speakers = await getSpeakers(year); + return speakers.map((speaker) => ({ year, speakerId: speaker.id })); + } catch (error) { + console.warn(`Failed to fetch speakers for year ${year}:`, error); + return []; } - } catch (error) { - console.warn(`Failed to fetch speakers for year ${year}:`, error); - } - } + }) + ); - return params; + return paramsArrays.flat(); } export async function generateMetadata({ params }: SpeakerDetailProps): Promise { diff --git a/app/[year]/tags/[tag]/page.tsx b/app/[year]/tags/[tag]/page.tsx index 35c74f61..40082357 100644 --- a/app/[year]/tags/[tag]/page.tsx +++ b/app/[year]/tags/[tag]/page.tsx @@ -18,27 +18,30 @@ export const dynamicParams = false; export async function generateStaticParams() { const years = getArchivedEditions(); - const params = []; - for (const year of years) { - try { - const sessionGroups = await getTalks(year); - const allTalks = sessionGroups.flatMap((group) => group.sessions); - const allTags = new Set(); - - for (const talk of allTalks) { - getTagsFromTalk(talk).forEach((tag) => allTags.add(tag)); - } - - for (const tag of allTags) { - params.push({ year, tag: tag.replaceAll(" ", "-").toLowerCase() }); + const paramsArrays = await Promise.all( + years.map(async (year) => { + try { + const sessionGroups = await getTalks(year); + const allTalks = sessionGroups.flatMap((group) => group.sessions); + const allTags = new Set(); + + for (const talk of allTalks) { + getTagsFromTalk(talk).forEach((tag) => allTags.add(tag)); + } + + return Array.from(allTags).map((tag) => ({ + year, + tag: tag.replaceAll(" ", "-").toLowerCase(), + })); + } catch (error) { + console.warn(`Failed to fetch talks for year ${year}:`, error); + return []; } - } catch (error) { - console.warn(`Failed to fetch talks for year ${year}:`, error); - } - } + }) + ); - return params; + return paramsArrays.flat(); } export async function generateMetadata({ params }: Readonly): Promise { diff --git a/app/[year]/talks/[talkId]/page.tsx b/app/[year]/talks/[talkId]/page.tsx index 4aecf5a4..a879da05 100644 --- a/app/[year]/talks/[talkId]/page.tsx +++ b/app/[year]/talks/[talkId]/page.tsx @@ -30,21 +30,21 @@ interface TalkDetailProps { export async function generateStaticParams() { const years = getArchivedEditions(); - const params = []; - - for (const year of years) { - try { - const sessionGroups = await getTalks(year); - const allTalks = sessionGroups.flatMap((group) => group.sessions); - for (const talk of allTalks) { - params.push({ year, talkId: talk.id }); + + const paramsArrays = await Promise.all( + years.map(async (year) => { + try { + const sessionGroups = await getTalks(year); + const allTalks = sessionGroups.flatMap((group) => group.sessions); + return allTalks.map((talk) => ({ year, talkId: talk.id })); + } catch (error) { + console.warn(`Failed to fetch talks for year ${year}:`, error); + return []; } - } catch (error) { - console.warn(`Failed to fetch talks for year ${year}:`, error); - } - } + }) + ); - return params; + return paramsArrays.flat(); } export async function generateMetadata({ params }: TalkDetailProps): Promise { From ba9c392797044f0f25be34ca9fd6c18d1f7d1274 Mon Sep 17 00:00:00 2001 From: anyulled <100741+anyulled@users.noreply.github.com> Date: Tue, 2 Jun 2026 08:51:38 +0000 Subject: [PATCH 2/2] feat: [performance improvement] Parallelize generateStaticParams fetching Replaced sequential for...of loops inside generateStaticParams with Promise.all mapping to fetch data concurrently. Applied formatting fix for .jules/bolt.md Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- .jules/bolt.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.jules/bolt.md b/.jules/bolt.md index 24587657..b3e847a9 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -2,6 +2,7 @@ **Learning:** In Next.js/React applications, when grouping items (like schedules or talks) into a `Map` where the values are arrays, using the array spread operator `[...existing, item]` inside a loop (like `forEach` or `map`) causes amortized O(N^2) memory allocations and unnecessary Garbage Collection overhead. **Action:** Always use `.push()` on the existing array reference if the data structure permits local mutation. For strict ESLint configurations enforcing `no-restricted-syntax`, extract the existing array, push to it, and handle the fallback elegantly (`if (!existing) { map.set(key, [item]); } else { existing.push(item); }`). + ## 2024-05-19 - Parallelize generateStaticParams fetching **Learning:** In Next.js applications, sequential data fetching (using `for...of` loops) inside build-critical functions like `generateStaticParams` significantly slows down static page generation. The bottleneck is amplified when fetching data across multiple parameters (e.g., archived years).