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-19 - Avoid redundant array string mapping in filtering loops

**Learning:** When matching string parameters (like a slugified `tag`) against an array of objects where each object contains an array of strings (like `tags`), pulling the parameter transformation (`decodedTag.toLowerCase()`) out of the loop and using `reduce` or localized `for...of` loops prevents O(N\*M) redundant string `.toLowerCase()` and `.replaceAll()` reallocations and improves both rendering and static site generation performance.
**Action:** When filtering objects by matching sub-properties against a parameter, extract the invariant transformation logic from the loop, and try to construct the derived `displayTag` variable and the `filteredTalks` array in a single loop traversal.
29 changes: 22 additions & 7 deletions app/2026/tags/[tag]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,16 @@ export async function generateMetadata({ params }: { params: Promise<{ tag: stri

const sessionGroups = await getTalks(year);
const allTalks = sessionGroups.flatMap((group) => group.sessions);
const displayTag =
allTalks.flatMap(getTagsFromTalk).find((t) => t.replaceAll(" ", "-").toLowerCase() === decodedTag.toLowerCase()) ?? decodedTag.replaceAll("-", " ");
const targetTag = decodedTag.toLowerCase();

const foundDisplayTag = allTalks.reduce<string | null>((acc, talk) => {
if (acc) return acc;
const talkTags = getTagsFromTalk(talk);
const matchedTag = talkTags.find((t) => t.replaceAll(" ", "-").toLowerCase() === targetTag);
return matchedTag ?? null;
}, null);
Comment on lines +45 to +50

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

Using Array.prototype.reduce here does not short-circuit once a match is found, meaning it will continue to iterate through the entire allTalks array even after acc is populated. Replacing this with a simple for...of loop allows you to break early, improving performance.

Suggested change
const foundDisplayTag = allTalks.reduce<string | null>((acc, talk) => {
if (acc) return acc;
const talkTags = getTagsFromTalk(talk);
const matchedTag = talkTags.find((t) => t.replaceAll(" ", "-").toLowerCase() === targetTag);
return matchedTag ?? null;
}, null);
let foundDisplayTag: string | null = null;
for (const talk of allTalks) {
const talkTags = getTagsFromTalk(talk);
const matchedTag = talkTags.find((t) => t.replaceAll(" ", "-").toLowerCase() === targetTag);
if (matchedTag) {
foundDisplayTag = matchedTag;
break;
}
}


const displayTag = foundDisplayTag ?? decodedTag.replaceAll("-", " ");

return {
title: `Talks tagged "${displayTag}" - DevBcn ${year}`,
Expand All @@ -58,14 +66,21 @@ export default async function Page({ params }: { params: Promise<{ tag: string }
const sessionGroups = await getTalks(year);
const allTalks = sessionGroups.flatMap((group) => group.sessions);

const displayTag =
allTalks.flatMap(getTagsFromTalk).find((t) => t.replaceAll(" ", "-").toLowerCase() === decodedTag.toLowerCase()) ?? decodedTag.replaceAll("-", " ");
const targetTag = decodedTag.toLowerCase();
const filteredTalks: typeof allTalks = [];

const filteredTalks = allTalks.filter((talk) => {
const foundDisplayTag = allTalks.reduce<string | null>((acc, talk) => {
const talkTags = getTagsFromTalk(talk);
const matchedTag = talkTags.find((t) => t.replaceAll(" ", "-").toLowerCase() === targetTag);

if (matchedTag) {
filteredTalks.push(talk);
}

return acc ?? matchedTag ?? null;
}, null);
Comment on lines +72 to +81

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

Using Array.prototype.reduce to perform side-effects (mutating the external filteredTalks array) is less idiomatic and harder to read. A standard for...of loop is more readable, performant, and aligns with the approach outlined in your plan.md.

Suggested change
const foundDisplayTag = allTalks.reduce<string | null>((acc, talk) => {
const talkTags = getTagsFromTalk(talk);
const matchedTag = talkTags.find((t) => t.replaceAll(" ", "-").toLowerCase() === targetTag);
if (matchedTag) {
filteredTalks.push(talk);
}
return acc ?? matchedTag ?? null;
}, null);
let foundDisplayTag: string | null = null;
for (const talk of allTalks) {
const talkTags = getTagsFromTalk(talk);
const matchedTag = talkTags.find((t) => t.replaceAll(" ", "-").toLowerCase() === targetTag);
if (matchedTag) {
filteredTalks.push(talk);
if (!foundDisplayTag) {
foundDisplayTag = matchedTag;
}
}
}


return talkTags.some((t) => t.replaceAll(" ", "-").toLowerCase() === decodedTag.toLowerCase());
});
const displayTag = foundDisplayTag ?? decodedTag.replaceAll("-", " ");

if (filteredTalks.length === 0) {
notFound();
Expand Down
29 changes: 22 additions & 7 deletions app/[year]/tags/[tag]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,16 @@ export async function generateMetadata({ params }: Readonly<TagPageProps>): Prom

const sessionGroups = await getTalks(year);
const allTalks = sessionGroups.flatMap((group) => group.sessions);
const displayTag =
allTalks.flatMap(getTagsFromTalk).find((t) => t.replaceAll(" ", "-").toLowerCase() === decodedTag.toLowerCase()) ?? decodedTag.replaceAll("-", " ");
const targetTag = decodedTag.toLowerCase();

const foundDisplayTag = allTalks.reduce<string | null>((acc, talk) => {
if (acc) return acc;
const talkTags = getTagsFromTalk(talk);
const matchedTag = talkTags.find((t) => t.replaceAll(" ", "-").toLowerCase() === targetTag);
return matchedTag ?? null;
}, null);
Comment on lines +52 to +57

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

Using Array.prototype.reduce here does not short-circuit once a match is found, meaning it will continue to iterate through the entire allTalks array even after acc is populated. Replacing this with a simple for...of loop allows you to break early, improving performance.

Suggested change
const foundDisplayTag = allTalks.reduce<string | null>((acc, talk) => {
if (acc) return acc;
const talkTags = getTagsFromTalk(talk);
const matchedTag = talkTags.find((t) => t.replaceAll(" ", "-").toLowerCase() === targetTag);
return matchedTag ?? null;
}, null);
let foundDisplayTag: string | null = null;
for (const talk of allTalks) {
const talkTags = getTagsFromTalk(talk);
const matchedTag = talkTags.find((t) => t.replaceAll(" ", "-").toLowerCase() === targetTag);
if (matchedTag) {
foundDisplayTag = matchedTag;
break;
}
}


const displayTag = foundDisplayTag ?? decodedTag.replaceAll("-", " ");

return {
title: `Talks tagged "${displayTag}" - DevBcn ${year}`,
Expand All @@ -64,14 +72,21 @@ export default async function TagPage({ params }: Readonly<TagPageProps>) {
const sessionGroups = await getTalks(year);
const allTalks = sessionGroups.flatMap((group) => group.sessions);

const displayTag =
allTalks.flatMap(getTagsFromTalk).find((t) => t.replaceAll(" ", "-").toLowerCase() === decodedTag.toLowerCase()) ?? decodedTag.replaceAll("-", " ");
const targetTag = decodedTag.toLowerCase();
const filteredTalks: typeof allTalks = [];

const filteredTalks = allTalks.filter((talk) => {
const foundDisplayTag = allTalks.reduce<string | null>((acc, talk) => {
const talkTags = getTagsFromTalk(talk);
const matchedTag = talkTags.find((t) => t.replaceAll(" ", "-").toLowerCase() === targetTag);

if (matchedTag) {
filteredTalks.push(talk);
}

return acc ?? matchedTag ?? null;
}, null);
Comment on lines +78 to +87

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

Using Array.prototype.reduce to perform side-effects (mutating the external filteredTalks array) is less idiomatic and harder to read. A standard for...of loop is more readable, performant, and aligns with the approach outlined in your plan.md.

Suggested change
const foundDisplayTag = allTalks.reduce<string | null>((acc, talk) => {
const talkTags = getTagsFromTalk(talk);
const matchedTag = talkTags.find((t) => t.replaceAll(" ", "-").toLowerCase() === targetTag);
if (matchedTag) {
filteredTalks.push(talk);
}
return acc ?? matchedTag ?? null;
}, null);
let foundDisplayTag: string | null = null;
for (const talk of allTalks) {
const talkTags = getTagsFromTalk(talk);
const matchedTag = talkTags.find((t) => t.replaceAll(" ", "-").toLowerCase() === targetTag);
if (matchedTag) {
filteredTalks.push(talk);
if (!foundDisplayTag) {
foundDisplayTag = matchedTag;
}
}
}


return talkTags.some((t) => t.replaceAll(" ", "-").toLowerCase() === decodedTag.toLowerCase());
});
const displayTag = foundDisplayTag ?? decodedTag.replaceAll("-", " ");

if (filteredTalks.length === 0) {
notFound();
Expand Down
Loading