From 0acb6b3d47eada67e733a041f650c3adf2719608 Mon Sep 17 00:00:00 2001 From: anyulled <100741+anyulled@users.noreply.github.com> Date: Mon, 15 Jun 2026 08:38:01 +0000 Subject: [PATCH] feat: optimize tag finding loop in app/[year]/tags/[tag]/page.tsx Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- .jules/bolt.md | 5 +++++ app/[year]/tags/[tag]/page.tsx | 31 ++++++++++++++++++++++++++----- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index 9345ee7a..e2e11609 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -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 flatMap and find for deep tag lookups in large datasets + +**Learning:** When searching for a specific item in nested arrays (like finding a specific tag across all talks), using `allTalks.flatMap(getTags).find(...)` creates unnecessary intermediate arrays and forces multiple full O(N) traversals. This is particularly slow during build-time operations like `generateStaticParams` or tag pages rendering with many talks. +**Action:** Use nested `for...of` loops (or `.some()` with early return and state accumulation) to do a single-pass O(N) search with early breakout to minimize Garbage Collection overhead and CPU cycles. diff --git a/app/[year]/tags/[tag]/page.tsx b/app/[year]/tags/[tag]/page.tsx index 35c74f61..5beed8c6 100644 --- a/app/[year]/tags/[tag]/page.tsx +++ b/app/[year]/tags/[tag]/page.tsx @@ -47,8 +47,21 @@ export async function generateMetadata({ params }: Readonly): 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 targetTagLower = decodedTag.toLowerCase(); + const state = { displayTag: undefined as string | undefined }; + + for (const talk of allTalks) { + const talkTags = getTagsFromTalk(talk); + for (const t of talkTags) { + if (t.replaceAll(" ", "-").toLowerCase() === targetTagLower) { + state.displayTag = t; + break; + } + } + if (state.displayTag) break; + } + + const displayTag = state.displayTag ?? decodedTag.replaceAll("-", " "); return { title: `Talks tagged "${displayTag}" - DevBcn ${year}`, @@ -64,15 +77,23 @@ export default async function TagPage({ params }: Readonly) { 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 targetTagLower = decodedTag.toLowerCase(); + const state = { displayTag: undefined as string | undefined }; const filteredTalks = allTalks.filter((talk) => { const talkTags = getTagsFromTalk(talk); - return talkTags.some((t) => t.replaceAll(" ", "-").toLowerCase() === decodedTag.toLowerCase()); + return talkTags.some((t) => { + if (t.replaceAll(" ", "-").toLowerCase() === targetTagLower) { + if (!state.displayTag) state.displayTag = t; + return true; + } + return false; + }); }); + const displayTag = state.displayTag ?? decodedTag.replaceAll("-", " "); + if (filteredTalks.length === 0) { notFound(); }