Skip to content
Closed
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 @@ -12,3 +12,8 @@

**Learning:** Found `useEffect` fetching static content (Speakers) in a Client Component (`Section5`) on the homepage. This caused unnecessary layout shifts and delayed LCP.
**Action:** Move data fetching to the parent Server Component (`page.tsx`) and pass data as props. This leverages ISR caching and eliminates client-side waterfall.

## 2026-02-01 - Sequential Data Fetching in Static Generation

**Learning:** Found sequential asynchronous loops inside `generateStaticParams` across multiple dynamic route segments (e.g. `[year]/talks/[talkId]/page.tsx`, `[year]/speakers/[speakerId]/page.tsx`). Iterating over a list of editions and awaiting data for each year one by one significantly delays static site generation (SSG) at build time.
**Action:** Always use `Promise.all` wrapped over an array `map` operation to parallelize independent data fetching operations across segments (like years or categories) in `generateStaticParams` or other build-time data preparation scripts. Make sure to map failures to empty arrays or valid fallbacks so a single failure doesn't break the entire parallel operation.
23 changes: 12 additions & 11 deletions app/[year]/speakers/[speakerId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,21 @@ interface SpeakerDetailProps {

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

for (const year of years) {
try {
const speakers = await getSpeakers(year);
for (const speaker of speakers) {
params.push({ year, speakerId: speaker.id });
// ⚡ Bolt: Fetch speaker data for all years in parallel to speed up static generation
const results = 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 results.flat();
}

export async function generateMetadata({ params }: SpeakerDetailProps): Promise<Metadata> {
Expand Down
37 changes: 19 additions & 18 deletions app/[year]/tags/[tag]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,28 @@ interface TagPageProps {

export async function generateStaticParams() {
const years = getAvailableEditions();
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: encodeURIComponent(tag) });
// ⚡ Bolt: Fetch talk data for all years in parallel to speed up static generation
const results = await Promise.all(
years.map(async (year) => {
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));
}
Comment on lines +28 to +32
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 logic for collecting all unique tags can be made more concise. You can use flatMap to get all tags from all talks and then initialize the Set directly from that array. This avoids the explicit for loop and forEach call, making the code more declarative and readable.

Suggested change
const allTags = new Set<string>();
for (const talk of allTalks) {
getTagsFromTalk(talk).forEach((tag) => allTags.add(tag));
}
const allTags = new Set<string>(allTalks.flatMap((talk) => getTagsFromTalk(talk)));


return Array.from(allTags).map((tag) => ({ year, tag: encodeURIComponent(tag) }));
} 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 results.flat();
}

export async function generateMetadata({ params }: TagPageProps): Promise<Metadata> {
Expand Down
27 changes: 14 additions & 13 deletions app/[year]/talks/[talkId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,22 @@ interface TalkDetailProps {

export async function generateStaticParams() {
const years = getAvailableEditions();
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 });

// ⚡ Bolt: Fetch talk data for all years in parallel to significantly speed up static generation (build time)
const results = 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 results.flat();
}

export async function generateMetadata({ params }: TalkDetailProps): Promise<Metadata> {
Expand Down
2 changes: 1 addition & 1 deletion cypress/e2e/home/home-editions.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ describe("Home Pages (2023-2026)", () => {
cy.visit(edition.path, { timeout: 120000 });

cy.get(".hero8-header", { timeout: 30000 }).within(() => {
cy.get("h5").should("have.length.at.least", 2);
cy.get(".hero8-header__event-line").should("have.length.at.least", 2);
cy.contains(edition.venue, { matchCase: false }).should("be.visible");
cy.contains(edition.date, { matchCase: false }).should("be.visible");
});
Expand Down