Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,8 @@

**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-31 - Optimize Next.js static generation builds with parallel data fetching

**Learning:** Sequential `for...of` loops used for fetching dynamic page dependencies inside Next.js build-time functions like `generateStaticParams` (or `sitemap.ts`) block the thread and significantly increase total site build duration.
**Action:** When gathering parameters for static routes across large array sets (such as mapping years or categories), replace sequential `for...of` iteration with parallel execution using `Promise.all(array.map(...))`. Then, correctly aggregate nested outputs utilizing `.flat()` or `.flatMap()`, and preserve per-item `try/catch` fallbacks to prevent a single item failure from crashing the entire batch build.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The recommendation to 'preserve per-item try/catch fallbacks to prevent a single item failure from crashing the entire batch build' contradicts the repository's general rule. We want data fetching failures during the build process to fail the build so that we do not deploy incomplete pages. Let's update this documentation to reflect the correct practice.

Suggested change
**Action:** When gathering parameters for static routes across large array sets (such as mapping years or categories), replace sequential `for...of` iteration with parallel execution using `Promise.all(array.map(...))`. Then, correctly aggregate nested outputs utilizing `.flat()` or `.flatMap()`, and preserve per-item `try/catch` fallbacks to prevent a single item failure from crashing the entire batch build.
**Action:** When gathering parameters for static routes across large array sets (such as mapping years or categories), replace sequential `for...of` iteration with parallel execution using `Promise.all(array.map(...))`. Then, correctly aggregate nested outputs utilizing `.flat()` or `.flatMap()`, and ensure data fetching functions run in strict mode without swallowing errors to let failures correctly fail the build and prevent deploying incomplete pages.
References
  1. Data fetching functions used during the build process (e.g., for sitemap generation) should include a 'strict' mode that throws an error on failure. This ensures that data fetching problems cause the build to fail, preventing the deployment of incomplete pages.

27 changes: 15 additions & 12 deletions app/[year]/job-offers/[companyName]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,22 @@ export const revalidate = 3600;

export async function generateStaticParams() {
const years = getAvailableEditions();
const params = [];

for (const year of years) {
const companies = await getJobOffersForEdition(year);
for (const company of companies) {
params.push({
year,
companyName: company.id,
});
}
}

return params;
const nestedParams = await Promise.all(
years.map(async (year) => {
const params: { year: string; companyName: string }[] = [];
const companies = await getJobOffersForEdition(year);
for (const company of companies) {
params.push({
year,
companyName: company.id,
});
}
return params;
})
);
Comment on lines +24 to +36
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Simplify the nested mapping using .map() instead of initializing an empty array and manually pushing to it. Additionally, to fully align with the repository's general rules, getJobOffersForEdition should ideally be updated to support a strict mode that throws on failure, though that can be addressed in a separate PR since public-queries.ts is not modified here.

  const nestedParams = await Promise.all(
    years.map(async (year) => {
      const companies = await getJobOffersForEdition(year);
      return companies.map((company) => ({
        year,
        companyName: company.id,
      }));
    })
  );


return nestedParams.flat();
}

export async function generateMetadata({ params }: CompanyJobOffersPageProps): Promise<Metadata> {
Expand Down
25 changes: 14 additions & 11 deletions app/[year]/speakers/[speakerId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,23 @@ 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 nestedParams = await Promise.all(
years.map(async (year) => {
const params: { year: string; speakerId: string }[] = [];
try {
const speakers = await getSpeakers(year);
for (const speaker of speakers) {
params.push({ year, speakerId: speaker.id });
}
} catch (error) {
console.warn(`Failed to fetch speakers for year ${year}:`, error);
}
} catch (error) {
console.warn(`Failed to fetch speakers for year ${year}:`, error);
}
}
return params;
})
);
Comment on lines +24 to +37
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The try/catch block is currently dead code because getSpeakers(year) does not throw errors unless { strict: true } is passed. Furthermore, according to the repository's general rules, data fetching functions used during the build process should run in strict mode to ensure that failures cause the build to fail, preventing the deployment of incomplete pages.

We should pass { strict: true } to getSpeakers and let the error propagate (removing the try/catch block). We can also simplify the array mapping using .map() instead of a manual loop and .push().

  const nestedParams = await Promise.all(
    years.map(async (year) => {
      const speakers = await getSpeakers(year, { strict: true });
      return speakers.map((speaker) => ({
        year,
        speakerId: speaker.id,
      }));
    })
  );
References
  1. Data fetching functions used during the build process (e.g., for sitemap generation) should include a 'strict' mode that throws an error on failure. This ensures that data fetching problems cause the build to fail, preventing the deployment of incomplete pages.


return params;
return nestedParams.flat();
}

export async function generateMetadata({ params }: SpeakerDetailProps): Promise<Metadata> {
Expand Down
39 changes: 21 additions & 18 deletions app/[year]/tags/[tag]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>();

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 nestedParams = await Promise.all(
years.map(async (year) => {
const params: { year: string; tag: string }[] = [];
try {
const sessionGroups = await getTalks(year);
const allTalks = sessionGroups.flatMap((group) => group.sessions);
const allTags = new Set<string>();

for (const talk of allTalks) {
getTagsFromTalk(talk).forEach((tag) => allTags.add(tag));
}

for (const tag of allTags) {
params.push({ year, tag: tag.replaceAll(" ", "-").toLowerCase() });
}
} catch (error) {
console.warn(`Failed to fetch talks for year ${year}:`, error);
}
} catch (error) {
console.warn(`Failed to fetch talks for year ${year}:`, error);
}
}
return params;
})
);
Comment on lines +22 to +42
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The try/catch block is currently dead code because getTalks(year) does not throw errors unless { strict: true } is passed. Furthermore, according to the repository's general rules, data fetching functions used during the build process should run in strict mode to ensure that failures cause the build to fail, preventing the deployment of incomplete pages.

We should pass { strict: true } to getTalks and let the error propagate (removing the try/catch block). We can also simplify the array mapping using .map() instead of a manual loop and .push().

  const nestedParams = await Promise.all(
    years.map(async (year) => {
      const sessionGroups = await getTalks(year, { strict: true });
      const allTalks = sessionGroups.flatMap((group) => group.sessions);
      const allTags = new Set<string>();

      for (const talk of allTalks) {
        getTagsFromTalk(talk).forEach((tag) => allTags.add(tag));
      }

      return Array.from(allTags).map((tag) => ({ 
        year,
        tag: tag.replaceAll(" ", "-").toLowerCase(),
      }));
    })
  );
References
  1. Data fetching functions used during the build process (e.g., for sitemap generation) should include a 'strict' mode that throws an error on failure. This ensures that data fetching problems cause the build to fail, preventing the deployment of incomplete pages.


return params;
return nestedParams.flat();
}

export async function generateMetadata({ params }: Readonly<TagPageProps>): Promise<Metadata> {
Expand Down
29 changes: 16 additions & 13 deletions app/[year]/talks/[talkId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,24 @@ 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 nestedParams = await Promise.all(
years.map(async (year) => {
const params: { year: string; talkId: string }[] = [];
try {
const sessionGroups = await getTalks(year);
const allTalks = sessionGroups.flatMap((group) => group.sessions);
for (const talk of allTalks) {
params.push({ year, talkId: talk.id });
}
} catch (error) {
console.warn(`Failed to fetch talks for year ${year}:`, error);
}
} catch (error) {
console.warn(`Failed to fetch talks for year ${year}:`, error);
}
}
return params;
})
);
Comment on lines +33 to +47
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The try/catch block is currently dead code because getTalks(year) does not throw errors unless { strict: true } is passed. Furthermore, according to the repository's general rules, data fetching functions used during the build process should run in strict mode to ensure that failures cause the build to fail, preventing the deployment of incomplete pages.

We should pass { strict: true } to getTalks and let the error propagate (removing the try/catch block). We can also simplify the array mapping using .map() instead of a manual loop and .push().

  const nestedParams = await Promise.all(
    years.map(async (year) => {
      const sessionGroups = await getTalks(year, { strict: true });
      const allTalks = sessionGroups.flatMap((group) => group.sessions);
      return allTalks.map((talk) => ({
        year,
        talkId: talk.id,
      }));
    })
  );
References
  1. Data fetching functions used during the build process (e.g., for sitemap generation) should include a 'strict' mode that throws an error on failure. This ensures that data fetching problems cause the build to fail, preventing the deployment of incomplete pages.


return params;
return nestedParams.flat();
}

export async function generateMetadata({ params }: TalkDetailProps): Promise<Metadata> {
Expand Down
Loading