Skip to content

⚡ Bolt: [Performance] Optimize sitemap generation with parallel execution#155

Open
anyulled wants to merge 1 commit intomainfrom
bolt-optimize-sitemap-generation-17187526585764795411
Open

⚡ Bolt: [Performance] Optimize sitemap generation with parallel execution#155
anyulled wants to merge 1 commit intomainfrom
bolt-optimize-sitemap-generation-17187526585764795411

Conversation

@anyulled
Copy link
Copy Markdown
Owner

@anyulled anyulled commented Apr 11, 2026

💡 What:
Refactored the data fetching within app/sitemap.ts from a sequential for...of loop to use Promise.all mapping over the list of editions, fetching getSpeakers and getTalks simultaneously for each year.

🎯 Why:
Previously, the sitemap generation created a waterfall sequence of API calls (fetching speakers then talks for one year, waiting, and proceeding to the next year sequentially). This introduces unnecessary I/O delays and degrades server response times when rendering sitemaps for SEO bots or during SSG build generation.

📊 Impact:
Significantly reduces execution time during both dynamic generation and static build time by performing independent API calls concurrently. Expected time complexity to fetch resources changes from O(N * M) sequentially to roughly O(M) concurrently where N is the number of years.

🔬 Measurement:
Can be verified by benchmarking the TTFB on the /sitemap.xml route or tracking Next.js build times locally using npm run build with timing logs around the sitemap resolution block.


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

Summary by CodeRabbit

  • Performance Improvements

    • Sitemap generation now fetches data concurrently instead of sequentially, reducing overall build time.
  • Reliability & Error Handling

    • Updated build process to properly propagate errors when external services become unavailable, ensuring builds fail cleanly instead of silently generating incomplete sitemaps.

Refactored sitemap generation to use `Promise.all` instead of sequential `for...of` loops, fetching data concurrently to speed up generation time while maintaining deterministic output order.

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
@google-labs-jules
Copy link
Copy Markdown
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.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 11, 2026

📝 Walkthrough

Walkthrough

The PR documents a behavior change for build-critical sitemap generation and refactors app/sitemap.ts to execute URL collection concurrently per year instead of sequentially. Fetching speakers and talks within each year now happens in parallel via Promise.all, with results concatenated at the end.

Changes

Cohort / File(s) Summary
Documentation
.jules/bolt.md
Added dated entry warning against swallowing Promise.all errors in build-critical routines; errors should propagate to fail CI/CD on external outages instead of generating incomplete outputs.
Sitemap Refactoring
app/sitemap.ts
Restructured URL construction to execute per-year collection concurrently via Promise.all(years.map(...)). Within each year, speaker and talk fetching changed from sequential awaits to parallel execution via Promise.all([getSpeakers(), getTalks()]). Final result concatenates baseUrls with flattened nestedUrls instead of a single loop.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Suggested labels

size/size/XXL

Poem

🐰 Hop along, dear sitemap route,
Concurrent paths now shoot and shoot!
No more waiting, year by year,
Promises dance—fast and clear! ✨

🚥 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 accurately describes the main change: optimizing sitemap generation through parallel execution of data fetches. It is specific, concise, and directly reflects the refactoring from sequential awaits to Promise.all concurrent execution documented in the changeset.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bolt-optimize-sitemap-generation-17187526585764795411

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.

@qodo-code-review
Copy link
Copy Markdown

ⓘ You are approaching your monthly quota for Qodo. Upgrade your plan

Review Summary by Qodo

Optimize sitemap generation with parallel data fetching

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Refactored sitemap generation to use Promise.all for parallel data fetching
• Fetches getSpeakers and getTalks concurrently per year instead sequentially
• Restructured code to map over years and collect results, improving performance
• Reduces time complexity from O(N * M) sequential to O(M) concurrent operations
Diagram
flowchart LR
  A["Sequential for...of loop"] -->|"Refactor"| B["Promise.all mapping"]
  B -->|"Parallel fetching"| C["getSpeakers & getTalks concurrently"]
  C -->|"Flatten results"| D["Combined sitemap output"]
Loading

Grey Divider

File Changes

1. app/sitemap.ts ✨ Enhancement +55/-49

Parallel data fetching for sitemap generation

• Replaced sequential for...of loop with Promise.all and map for concurrent year processing
• Moved getSpeakers and getTalks calls into a single Promise.all to fetch data in parallel per
 year
• Refactored variable naming from urls to baseUrls and nestedUrls for clarity
• Flattened and combined results using spread operator to maintain deterministic output order

app/sitemap.ts


2. .jules/bolt.md 📝 Documentation +5/-0

Document error handling best practices for sitemaps

• Added learning note about error handling in Promise.all for build-critical files
• Documented risk of silently incomplete builds when swallowing API errors with .catch(() => [])
• Recommended letting API fetching errors propagate to ensure CI/CD pipeline failures on external
 outages

.jules/bolt.md


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented Apr 11, 2026

Code Review by Qodo

🐞 Bugs (2)   📘 Rule violations (1)   📎 Requirement gaps (0)   🎨 UX Issues (0)
🐞\ ☼ Reliability (2)
📘\ ☼ Reliability (1)

Grey Divider


Action required

1. Sitemap hides fetch failures 🐞
Description
sitemap() uses getSpeakers()/getTalks() inside Promise.all, but both helpers swallow
network/non-OK errors and return empty arrays, so /sitemap.xml can be generated missing dynamic
URLs without failing the build/request. This can lead to silently incomplete sitemaps (SEO impact)
during upstream outages.
Code

app/sitemap.ts[54]

+      const [speakers, sessionGroups] = await Promise.all([getSpeakers(year), getTalks(year)]);
Evidence
In app/sitemap.ts, sitemap generation depends on getSpeakers(year) and getTalks(year) results,
but those functions never throw: they catch errors and also return [] on non-OK HTTP responses.
Therefore sitemap generation will succeed even when upstream is failing, producing an incomplete
sitemap without a hard failure signal.

app/sitemap.ts[33-75]
hooks/useSpeakers.ts[14-28]
hooks/useTalks.ts[16-30]
.jules/bolt.md[21-24]

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

## Issue description
`sitemap()` can silently generate an incomplete sitemap because `getSpeakers()` and `getTalks()` swallow failures (returning `[]` on errors/non-OK responses). This prevents CI/builds (and runtime sitemap generation) from failing loudly when upstream Sessionize is unavailable.

## Issue Context
- `app/sitemap.ts` uses `getSpeakers(year)` and `getTalks(year)` to build dynamic URLs.
- `hooks/useSpeakers.ts` and `hooks/useTalks.ts` both `try/catch` and also return `[]` when `response.ok` is false.
- `.jules/bolt.md` explicitly calls out this failure mode as harmful for sitemaps.

## Fix Focus Areas
- Create strict variants that throw on failure (e.g., `getSpeakersOrThrow`, `getTalksOrThrow`) and use them from sitemap generation.
- Alternatively, add an option parameter to `getSpeakers/getTalks` (e.g., `{ swallowErrors: boolean }`) and pass `false` from `sitemap()`.
- Ensure non-OK responses throw (include status/statusText) so `Promise.all` rejects and the build/request fails.

### Fix Focus Areas (code references)
- app/sitemap.ts[33-75]
- hooks/useSpeakers.ts[14-28]
- hooks/useTalks.ts[16-30]
- .jules/bolt.md[21-24]

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



Remediation recommended

2. Promise.all lacks try/catch 📘
Description
New parallel fetching uses await Promise.all(...) without a surrounding try/catch or explicit
.catch(...), so rejections are not explicitly handled per the checklist requirement. This can
result in unhandled promise rejection behavior depending on the caller/framework and makes
error-handling intent unclear.
Code

app/sitemap.ts[R33-55]

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

-    const yearPages = ["speakers", "talks", "schedule", "job-offers", "cfp", "diversity", "sponsorship", "travel"];
-    for (const page of yearPages) {
-      urls.push({
-        url: `${baseUrl}/${year}/${page}`,
+      yearUrls.push({
+        url: `${baseUrl}/${year}`,
        lastModified: BUILD_TIME,
-        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: BUILD_TIME,
-        changeFrequency: "weekly",
-        priority: 0.7,
-      });
-    }
+      const yearPages = ["speakers", "talks", "schedule", "job-offers", "cfp", "diversity", "sponsorship", "travel"];
+      for (const page of yearPages) {
+        yearUrls.push({
+          url: `${baseUrl}/${year}/${page}`,
+          lastModified: BUILD_TIME,
+          changeFrequency: "weekly",
+          priority: 0.8,
+        });
+      }

-    const sessionGroups = await getTalks(year);
-    for (const group of sessionGroups) {
-      for (const talk of group.sessions) {
-        urls.push({
-          url: `${baseUrl}/${year}/talks/${talk.id}`,
+      const [speakers, sessionGroups] = await Promise.all([getSpeakers(year), getTalks(year)]);
+
Evidence
PR Compliance ID 95937 requires promises to be awaited inside a try/catch (or to use an explicit
.catch(...)/central helper). The added code awaits two Promise.all calls for getSpeakers(year)
and getTalks(year) without any explicit rejection handling.

Rule 95937: Handle promise rejections explicitly
app/sitemap.ts[33-55]

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

## Issue description
`await Promise.all(...)` is used without explicit rejection handling (no surrounding `try/catch` and no `.catch(...)`), which violates the promise rejection handling requirement.

## Issue Context
This was introduced in the refactor that parallelizes sitemap data fetching. Errors should be handled explicitly (either logged + rethrown, or converted into an intentional fallback).

## Fix Focus Areas
- app/sitemap.ts[33-55]

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


3. Unbounded year parallelism 🐞
Description
Promise.all(years.map(...)) starts all edition sitemap work concurrently, and each edition
triggers two upstream fetches; as the number of editions grows this can overload the upstream API or
hit platform connection limits. This can cause sitemap generation timeouts/flakiness under load.
Code

app/sitemap.ts[R33-35]

+  const nestedUrls = await Promise.all(
+    years.map(async (year) => {
+      const yearUrls: MetadataRoute.Sitemap = [];
Evidence
getAvailableEditions() returns all known editions, and sitemap() now maps them into a single
Promise.all (all editions at once). Each mapped task performs `Promise.all([getSpeakers(year),
getTalks(year)])`, so concurrency scales as ~2N simultaneous upstream fetches for N editions.

app/sitemap.ts[33-55]
config/editions/index.ts[67-69]

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

## Issue description
`sitemap()` launches work for all editions at once via `Promise.all(years.map(...))`, which scales concurrency linearly with the number of editions (and doubles it due to speakers+talks fetches per edition). This can reduce reliability due to upstream rate limiting or connection limits.

## Issue Context
- Current behavior: unbounded parallelism across all editions.
- Desired behavior: keep parallelism, but cap concurrency (e.g., process editions in small batches or use a small semaphore).

## Fix Focus Areas
- Implement simple batching (e.g., chunk `years` into groups of 2-3, `await Promise.all` per chunk).
- Or introduce a small internal limiter (avoid adding dependencies if the repo prefers).
- Add an inline comment documenting why the chosen concurrency is safe.

### Fix Focus Areas (code references)
- app/sitemap.ts[33-88]
- config/editions/index.ts[67-69]

ⓘ 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
Copy Markdown
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 refactors the sitemap generation to use parallel data fetching for improved performance and adds a new policy to the documentation regarding the handling of API errors in build-critical files. A review comment notes that the current implementation of data fetching functions may still be swallowing exceptions, which contradicts the newly established policy to let errors propagate and fail the build during outages.

Comment thread app/sitemap.ts
for (const talk of group.sessions) {
urls.push({
url: `${baseUrl}/${year}/talks/${talk.id}`,
const [speakers, sessionGroups] = await Promise.all([getSpeakers(year), getTalks(year)]);
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 implementation of parallel fetching here relies on getSpeakers and getTalks, which currently swallow exceptions and return empty arrays on failure (as seen in the provided context for hooks/useSpeakers.ts and hooks/useTalks.ts). This directly conflicts with the new policy being established in .jules/bolt.md (lines 23-24) regarding 'Silently Failing Sitemaps'. To ensure the build fails accurately upon API outages as intended, these hooks should be refactored to propagate errors, or this block should validate the responses to ensure they are not empty due to a fetch failure.

Copy link
Copy Markdown
Contributor

@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

🧹 Nitpick comments (1)
app/sitemap.ts (1)

33-34: Consider bounding year-level concurrency to avoid request bursts.

Promise.all(years.map(...)) fires all edition fetches at once; as editions grow, this can create avoidable upstream throttling risk.

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

In `@app/sitemap.ts` around lines 33 - 34, The current Promise.all(years.map(...))
in sitemap generation launches all year-level fetches concurrently (nestedUrls),
which risks request bursts; replace it with a bounded-concurrency mapper (or
batching) so only a fixed number (e.g., 5-10) of year fetches run in parallel.
Locate the years.map callback used to build nestedUrls and swap the
Promise.all(years.map(...)) call for a concurrency-controlled implementation
(for example, using p-map with a concurrency option, or an internal async
pool/batched for-loop) so the function that fetches editions per year runs with
the chosen concurrency limit.
🤖 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 54-55: The sitemap currently proceeds when getSpeakers(year) or
getTalks(year) fail (they return []), so wrap the
Promise.all([getSpeakers(year), getTalks(year)]) call in a try/catch and
validate the results (speakers and sessionGroups) before generating URLs; if
either is missing/empty, throw a descriptive error to fail the build (or rethrow
the caught error) so app/sitemap.ts doesn't emit a partial sitemap. Reference
the getSpeakers and getTalks calls and the speakers/sessionGroups variables when
adding the validation and error handling.

---

Nitpick comments:
In `@app/sitemap.ts`:
- Around line 33-34: The current Promise.all(years.map(...)) in sitemap
generation launches all year-level fetches concurrently (nestedUrls), which
risks request bursts; replace it with a bounded-concurrency mapper (or batching)
so only a fixed number (e.g., 5-10) of year fetches run in parallel. Locate the
years.map callback used to build nestedUrls and swap the
Promise.all(years.map(...)) call for a concurrency-controlled implementation
(for example, using p-map with a concurrency option, or an internal async
pool/batched for-loop) so the function that fetches editions per year runs with
the chosen concurrency limit.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f8b0dd79-6726-49e5-83c7-43f809ec9cfb

📥 Commits

Reviewing files that changed from the base of the PR and between 4a848cd and 66b1420.

📒 Files selected for processing (2)
  • .jules/bolt.md
  • app/sitemap.ts

Comment thread app/sitemap.ts
Comment on lines +54 to +55
const [speakers, sessionGroups] = await Promise.all([getSpeakers(year), getTalks(year)]);

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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether sitemap data helpers swallow failures by returning [].
fd -i 'useSpeakers.ts' -t f --exec sed -n '1,120p' {}
fd -i 'useTalks.ts' -t f --exec sed -n '1,140p' {}
rg -n "catch\s*\(|return\s*\[\]|Failed to fetch" --type ts

Repository: anyulled/devbcn-nextjs

Length of output: 8362


🏁 Script executed:

fd -i 'sitemap.ts' -t f --exec cat -n {}

Repository: anyulled/devbcn-nextjs

Length of output: 3285


Sitemap generation silently succeeds with partial data during fetch failures.

At line 54, getSpeakers(year) and getTalks(year) are called without error handling. Both helpers catch failures and return empty arrays (see hooks/useSpeakers.ts and hooks/useTalks.ts), allowing the sitemap to be generated incomplete if either fetch fails. The sitemap then iterates these arrays (lines 56–73), generating fewer URLs than expected without failing the build. During outages, this results in a degraded sitemap being deployed instead of build failure.

Either wrap the Promise.all in try/catch to fail the build on critical fetch errors, add explicit validation that speakers and sessionGroups are non-empty before generating URLs, or ensure the helpers throw on errors instead of returning empty arrays.

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

In `@app/sitemap.ts` around lines 54 - 55, The sitemap currently proceeds when
getSpeakers(year) or getTalks(year) fail (they return []), so wrap the
Promise.all([getSpeakers(year), getTalks(year)]) call in a try/catch and
validate the results (speakers and sessionGroups) before generating URLs; if
either is missing/empty, throw a descriptive error to fail the build (or rethrow
the caught error) so app/sitemap.ts doesn't emit a partial sitemap. Reference
the getSpeakers and getTalks calls and the speakers/sessionGroups variables when
adding the validation and error handling.

Comment thread app/sitemap.ts
for (const talk of group.sessions) {
urls.push({
url: `${baseUrl}/${year}/talks/${talk.id}`,
const [speakers, sessionGroups] = await Promise.all([getSpeakers(year), getTalks(year)]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Sitemap hides fetch failures 🐞 Bug ☼ Reliability

sitemap() uses getSpeakers()/getTalks() inside Promise.all, but both helpers swallow
network/non-OK errors and return empty arrays, so /sitemap.xml can be generated missing dynamic
URLs without failing the build/request. This can lead to silently incomplete sitemaps (SEO impact)
during upstream outages.
Agent Prompt
## Issue description
`sitemap()` can silently generate an incomplete sitemap because `getSpeakers()` and `getTalks()` swallow failures (returning `[]` on errors/non-OK responses). This prevents CI/builds (and runtime sitemap generation) from failing loudly when upstream Sessionize is unavailable.

## Issue Context
- `app/sitemap.ts` uses `getSpeakers(year)` and `getTalks(year)` to build dynamic URLs.
- `hooks/useSpeakers.ts` and `hooks/useTalks.ts` both `try/catch` and also return `[]` when `response.ok` is false.
- `.jules/bolt.md` explicitly calls out this failure mode as harmful for sitemaps.

## Fix Focus Areas
- Create strict variants that throw on failure (e.g., `getSpeakersOrThrow`, `getTalksOrThrow`) and use them from sitemap generation.
- Alternatively, add an option parameter to `getSpeakers/getTalks` (e.g., `{ swallowErrors: boolean }`) and pass `false` from `sitemap()`.
- Ensure non-OK responses throw (include status/statusText) so `Promise.all` rejects and the build/request fails.

### Fix Focus Areas (code references)
- app/sitemap.ts[33-75]
- hooks/useSpeakers.ts[14-28]
- hooks/useTalks.ts[16-30]
- .jules/bolt.md[21-24]

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

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant