Skip to content

⚡ Bolt: Optimize sitemap generation with parallel data fetching#97

Open
anyulled wants to merge 2 commits intomainfrom
bolt/optimize-sitemap-generation-17970766081438447557
Open

⚡ Bolt: Optimize sitemap generation with parallel data fetching#97
anyulled wants to merge 2 commits intomainfrom
bolt/optimize-sitemap-generation-17970766081438447557

Conversation

@anyulled
Copy link
Owner

@anyulled anyulled commented Mar 16, 2026

💡 What:
Replaced sequential data fetching (getSpeakers and getTalks) inside a for...of loop with a mapping that uses Promise.all to fetch data concurrently.

🎯 Why:
The previous implementation fetched data year-by-year, and sequentially within each year. This created a massive API waterfall that drastically increased sitemap generation time as the number of years and sessions grew. By parallelizing the fetches, the sitemap generated much faster.

📊 Impact:
Eliminates sequential requests overhead for N years and 2 main data calls per year. The sitemap generation is now limited only by the slowest single request, drastically reducing Next.js build and ISR time.

🔬 Measurement:
Run npm run build and observe the reduced build time for the sitemap.xml static page generation. All URLs should be correctly populated as before.


PR created automatically by Jules for task 17970766081438447557 started by @anyulled

Summary by CodeRabbit

  • Refactor
    • Sitemap now generates per-year URL groups and includes expanded page coverage for speakers, talks, schedules, job offers, CFP, diversity, sponsorship, and travel.
    • URL structure for speaker and talk pages adjusted to per-year endpoints; talk listings now derived from session groupings.
  • Performance & Reliability
    • Parallelized fetching for faster sitemap builds and graceful handling of missing data.
  • SEO
    • Updated update cadences and priority settings for included pages.

Replaced the sequential `for...of` loop in `app/sitemap.ts` with parallel
execution via `Promise.all`. This allows `getSpeakers` and `getTalks` to be
fetched simultaneously for each year, greatly reducing overall sitemap build time.
Handled error states gracefully for individual items using `.catch(() => [])`.

Co-authored-by: anyulled <100741+anyulled@users.noreply.github.com>
@google-labs-jules
Copy link
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@vercel
Copy link

vercel bot commented Mar 16, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
devbcn Ready Ready Preview, Comment Mar 16, 2026 9:39am

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the performance of sitemap generation by transitioning from a sequential data fetching model to a parallelized approach. By leveraging Promise.all, the system now fetches speaker and talk data concurrently across different years, thereby eliminating bottlenecks caused by an API waterfall and drastically reducing build and Incremental Static Regeneration (ISR) times for the sitemap.

Highlights

  • Optimized Sitemap Generation: The sitemap generation process was refactored to replace sequential data fetching with concurrent requests using Promise.all, significantly improving performance.
  • Concurrent Data Fetching: Data for speakers and talks, previously fetched year-by-year and sequentially, is now fetched in parallel for each year, reducing API waterfall effects.
Changelog
  • app/sitemap.ts
    • Converted the main for...of loop iterating over years into a Promise.all call mapping over the years array.
    • Introduced Promise.all within each year's processing to concurrently fetch speaker and talk data (getSpeakers and getTalks).
    • Added error handling (.catch(() => [])) to getSpeakers and getTalks calls within the parallel fetch to prevent failures from stopping the entire process.
    • Modified the final return statement to concatenate the results from the parallel year processing.
Activity
  • PR created automatically by Jules for task 17970766081438447557 started by @anyulled
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai
Copy link

coderabbitai bot commented Mar 16, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: dbbf3b21-58f0-443a-8f71-a81d682df52e

📥 Commits

Reviewing files that changed from the base of the PR and between 17731bc and 0ae9e7d.

📒 Files selected for processing (1)
  • app/sitemap.ts

📝 Walkthrough

Walkthrough

Sitemap generation in app/sitemap.ts was rewritten to generate per-year URLs in parallel. It concurrently fetches speakers, talks, and job offers per year, builds expanded per-year routes (speakers, talks, schedule, job-offers, cfp, diversity, sponsorship, travel), and returns a flattened sitemap array.

Changes

Cohort / File(s) Summary
Sitemap Generation
app/sitemap.ts
Replaced sequential per-year URL assembly with parallel per-year generation using Promise.all; concurrently fetches getSpeakers, getTalks, and job offers per year (with failure fallbacks); adds per-year pages (/year/speakers, /year/talks, /year/schedule, /year/job-offers, /year/cfp, /year/diversity, /year/sponsorship, /year/travel) with specified changefreq/priority; uses sessionGroups for talk URLs; concatenates per-year URL arrays into final sitemap.

Sequence Diagram(s)

sequenceDiagram
    rect rgba(240,248,255,0.5)
    participant Sitemap as app/sitemap.ts
    participant Speakers as getSpeakers(year)
    participant Talks as getTalks(year)
    participant Jobs as getJobOffers(year)
    participant Builder as URL Builder
    participant Output as Sitemap Array
    end

    Sitemap->>Speakers: request speakers for year (concurrent)
    par Parallel fetches
        Sitemap->>Talks: request talks for year (concurrent)
        Sitemap->>Jobs: request job-offers for year (concurrent)
    end
    Speakers-->>Sitemap: speakers[] or []
    Talks-->>Sitemap: talks/sessionGroups[] or []
    Jobs-->>Sitemap: jobOffers[] or []
    Sitemap->>Builder: build per-year URLs (pages + per-item routes)
    Builder-->>Output: per-year URL list
    Sitemap->>Output: concatenate all per-year lists -> return sitemap
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Poem

🐰 I hopped through promises, swift and bright,
Split the work and leapt in flight,
Speakers, talks, and jobs in a spin,
All stitched into one tidy bin,
Sitemap done — a rabbit's delight!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: optimizing sitemap generation through parallel data fetching, which aligns with the primary goal of replacing sequential requests with Promise.all for concurrent data fetches.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch bolt/optimize-sitemap-generation-17970766081438447557
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

CodeRabbit can use your project's `biome` configuration to improve the quality of JS/TS/CSS/JSON code reviews.

Add a configuration file to your project to customize how CodeRabbit runs biome.

@qodo-code-review
Copy link

Review Summary by Qodo

Optimize sitemap generation with parallel data fetching

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Parallelize data fetching for speakers and talks using Promise.all
• Replace sequential for...of loop with concurrent year processing
• Add error handling with .catch() for individual data fetch failures
• Reduce sitemap generation time by eliminating API waterfall pattern
Diagram
flowchart LR
  A["Sequential for...of loop<br/>year by year"] -->|"Refactored to"| B["Promise.all<br/>parallel years"]
  B --> C["getSpeakers & getTalks<br/>concurrent per year"]
  C --> D["Error handling<br/>with .catch"]
  D --> E["Faster sitemap<br/>generation"]
Loading

Grey Divider

File Changes

1. app/sitemap.ts ✨ Enhancement +47/-38

Parallelize year-based sitemap URL generation

• Converted sequential year iteration to parallel Promise.all mapping
• Moved all year-specific URL generation into async map callback
• Combined getSpeakers and getTalks into single Promise.all with error handling
• Flattened yearUrls array results and concatenated with base urls

app/sitemap.ts


Grey Divider

Qodo Logo

@qodo-code-review
Copy link

qodo-code-review bot commented Mar 16, 2026

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Remediation recommended

1. Unbounded fetch concurrency 🐞 Bug ⛯ Reliability
Description
sitemap() now uses a top-level Promise.all(years.map(...)), causing all editions to fetch and
JSON-parse speakers and talks concurrently, increasing peak memory and simultaneous outbound
requests. Because getSpeakers/getTalks return [] on fetch errors, transient failures under
higher concurrency will silently drop URLs from the generated sitemap.
Code

app/sitemap.ts[R31-55]

+  const yearUrls = await Promise.all(
+    years.map(async (year) => {
+      const urls: MetadataRoute.Sitemap = [];

-    const yearPages = ["speakers", "talks", "schedule", "job-offers", "cfp", "diversity", "sponsorship", "travel"];
-    for (const page of yearPages) {
     urls.push({
-        url: `${baseUrl}/${year}/${page}`,
+        url: `${baseUrl}/${year}`,
       lastModified: new Date(),
-        changeFrequency: "weekly",
-        priority: 0.8,
+        changeFrequency: "daily",
+        priority: 0.9,
     });
-    }

-    const speakers = await getSpeakers(year);
-    for (const speaker of speakers) {
-      urls.push({
-        url: `${baseUrl}/${year}/speakers/${speaker.id}`,
-        lastModified: new Date(),
-        changeFrequency: "weekly",
-        priority: 0.7,
-      });
-    }
+      const yearPages = ["speakers", "talks", "schedule", "job-offers", "cfp", "diversity", "sponsorship", "travel"];
+      for (const page of yearPages) {
+        urls.push({
+          url: `${baseUrl}/${year}/${page}`,
+          lastModified: new Date(),
+          changeFrequency: "weekly",
+          priority: 0.8,
+        });
+      }
+
+      const [speakers, sessionGroups] = await Promise.all([
+        getSpeakers(year).catch(() => []),
+        getTalks(year).catch(() => [])
+      ]);
Evidence
The sitemap refactor parallelizes processing across all available editions and, within each edition,
parallelizes both Sessionize endpoints; those endpoints are fetched and fully materialized via
response.json(). Fetch failures in the hooks produce empty arrays, which results in no
speaker/talk URLs being added for that year, so any increase in transient failures (e.g., from burst
concurrency) directly reduces sitemap coverage without failing the sitemap build.

app/sitemap.ts[31-55]
config/editions/index.ts[21-27]
config/editions/index.ts[65-67]
hooks/useSpeakers.ts[14-32]
hooks/useTalks.ts[16-34]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`app/sitemap.ts` parallelizes sitemap building across all editions with `Promise.all(years.map(...))`. This causes simultaneous fetch+JSON parsing for speakers and talks for every edition, increasing peak memory and outbound request fan-out and making transient upstream failures more likely to result in silently missing sitemap entries.
### Issue Context
- `getSpeakers()` and `getTalks()` fetch remote Sessionize endpoints and materialize the full payload via `response.json()`.
- Both hooks return `[]` on failure, so any failed request yields missing speaker/talk URLs for that year.
### Fix Focus Areas
- app/sitemap.ts[31-55]
### Suggested fix approach
- Keep per-year parallelism (`Promise.all([getSpeakers(year), getTalks(year)])`).
- Limit cross-year concurrency, e.g.:
- Process years sequentially in a `for...of` loop, OR
- Use a small concurrency limiter (e.g., 2) to run only a couple of years at once.
- (Optional) If you keep `.catch(() =&amp;gt; [])`, add explicit logging in sitemap for year-level failures so sitemap omissions are observable.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Advisory comments

2. Concat allocates extra array 🐞 Bug ➹ Performance
Description
return urls.concat(...yearUrls) allocates a new array and copies all entries, increasing CPU and
peak memory during sitemap generation. Appending year results into the existing urls array avoids
this extra allocation.
Code

app/sitemap.ts[91]

+  return urls.concat(...yearUrls);
Evidence
The implementation builds per-year arrays, then returns a concatenated copy rather than appending
into the existing output array.

app/sitemap.ts[31-33]
app/sitemap.ts[91-91]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`urls.concat(...yearUrls)` creates a new array and copies all sitemap entries into it.
### Issue Context
This is a minor but easy win when the sitemap grows.
### Fix Focus Areas
- app/sitemap.ts[91-91]
### Suggested fix
Replace the return with an append and return:
- `for (const yr of yearUrls) urls.push(...yr);`
- `return urls;`

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request significantly improves the performance of sitemap generation by replacing sequential, year-by-year data fetching with parallel processing using Promise.all. The implementation is clean, effective, and includes robust error handling with .catch() to prevent single failures from breaking the entire process. This is an excellent optimization that will noticeably reduce build times. I have one minor suggestion to further enhance consistency and performance.

urls.push({
url: `${baseUrl}/${year}/${page}`,
url: `${baseUrl}/${year}`,
lastModified: new Date(),
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

For consistency and a minor performance improvement, consider creating a single Date object at the start of the sitemap function (e.g., const now = new Date();) and reusing it for all lastModified properties. This ensures all URLs generated in this run have the same timestamp and avoids creating unnecessary Date objects inside loops.

Replaced the sequential `for...of` loop in `app/sitemap.ts` with parallel
execution via `Promise.all`. This allows `getSpeakers` and `getTalks` to be
fetched simultaneously for each year, greatly reducing overall sitemap build time.
Handled error states gracefully for individual items using `.catch(() => [])`. Fixed prettier formatting issues.

Co-authored-by: anyulled <100741+anyulled@users.noreply.github.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/sitemap.ts (1)

1-92: ⚠️ Potential issue | 🟡 Minor

Fix Prettier formatting to resolve CI failure.

The pipeline is failing due to Prettier formatting issues. Run prettier --write app/sitemap.ts to fix the code style.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/sitemap.ts` around lines 1 - 92, Prettier formatting errors in
app/sitemap.ts are causing CI to fail; run prettier --write on the file (or
format it in your editor) and commit the changes so the exported async function
sitemap, import list (getJobOffersByYear, getAvailableEditions, getSpeakers,
getTalks, slugify) and all object literals/arrays conform to the project's
Prettier rules; after formatting, re-run the CI/lint step to ensure no remaining
style issues before pushing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/sitemap.ts`:
- Around line 52-55: The Promise.all catch handlers silently swallow errors when
calling getSpeakers(year) and getTalks(year); update the .catch handlers on the
Promise.all input (the entries for getSpeakers and getTalks) to accept an error
parameter and log it (using the project's logger, e.g., processLogger.error or
console.error) before returning the fallback []; ensure you reference
getSpeakers and getTalks in sitemap.ts so failures are logged with context like
"getSpeakers failed" / "getTalks failed" and still return [] to preserve
behavior.

---

Outside diff comments:
In `@app/sitemap.ts`:
- Around line 1-92: Prettier formatting errors in app/sitemap.ts are causing CI
to fail; run prettier --write on the file (or format it in your editor) and
commit the changes so the exported async function sitemap, import list
(getJobOffersByYear, getAvailableEditions, getSpeakers, getTalks, slugify) and
all object literals/arrays conform to the project's Prettier rules; after
formatting, re-run the CI/lint step to ensure no remaining style issues before
pushing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ee584938-2ff5-42fc-8cf2-ff5aeade20b8

📥 Commits

Reviewing files that changed from the base of the PR and between ae91a99 and 17731bc.

📒 Files selected for processing (1)
  • app/sitemap.ts

app/sitemap.ts Outdated
Comment on lines +52 to +55
const [speakers, sessionGroups] = await Promise.all([
getSpeakers(year).catch(() => []),
getTalks(year).catch(() => [])
]);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add error logging to catch handlers.

The .catch(() => []) handlers silently swallow errors without logging, violating the coding guideline that prohibits generic catch-all statements without proper error handling.

While the underlying getSpeakers and getTalks functions already log errors internally, unexpected rejection reasons could still be silently lost here.

Suggested fix
       const [speakers, sessionGroups] = await Promise.all([
-        getSpeakers(year).catch(() => []),
-        getTalks(year).catch(() => [])
+        getSpeakers(year).catch((error) => {
+          console.error(`Failed to fetch speakers for sitemap (year ${year}):`, error);
+          return [];
+        }),
+        getTalks(year).catch((error) => {
+          console.error(`Failed to fetch talks for sitemap (year ${year}):`, error);
+          return [];
+        })
       ]);

As per coding guidelines: "Avoid unhandled promise rejections. Do not use generic catch-all statements without logging or handling the error properly."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const [speakers, sessionGroups] = await Promise.all([
getSpeakers(year).catch(() => []),
getTalks(year).catch(() => [])
]);
const [speakers, sessionGroups] = await Promise.all([
getSpeakers(year).catch((error) => {
console.error(`Failed to fetch speakers for sitemap (year ${year}):`, error);
return [];
}),
getTalks(year).catch((error) => {
console.error(`Failed to fetch talks for sitemap (year ${year}):`, error);
return [];
})
]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/sitemap.ts` around lines 52 - 55, The Promise.all catch handlers silently
swallow errors when calling getSpeakers(year) and getTalks(year); update the
.catch handlers on the Promise.all input (the entries for getSpeakers and
getTalks) to accept an error parameter and log it (using the project's logger,
e.g., processLogger.error or console.error) before returning the fallback [];
ensure you reference getSpeakers and getTalks in sitemap.ts so failures are
logged with context like "getSpeakers failed" / "getTalks failed" and still
return [] to preserve behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant