Skip to content

⚡ Bolt: Optimize sitemap generation using Promise.all#80

Open
anyulled wants to merge 7 commits intomainfrom
bolt/optimize-sitemap-parallel-fetch-14425354385331826874
Open

⚡ Bolt: Optimize sitemap generation using Promise.all#80
anyulled wants to merge 7 commits intomainfrom
bolt/optimize-sitemap-parallel-fetch-14425354385331826874

Conversation

@anyulled
Copy link
Owner

@anyulled anyulled commented Mar 8, 2026

💡 What: Refactored app/sitemap.ts to fetch speakers and talks data concurrently using Promise.all across years and resource types. Also added fallback empty arrays to handle partial API failures without crashing the entire sitemap generation.
🎯 Why: Sequential await statements inside the loops caused significant I/O blocking, making sitemap generation time linearly dependent on the number of years and resources.
📊 Impact: drastically reduces sitemap generation time by overlapping network and I/O wait times.
🔬 Measurement: Compare sitemap generation build times before and after this change.


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

Summary by CodeRabbit

  • Tests

    • Updated test selectors for homepage editions validation.
  • Chores

    • Removed explicit dependency version override from package configuration.

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 8, 2026

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

Project Deployment Actions Updated (UTC)
devbcn Ready Ready Preview, Comment Mar 8, 2026 10:35am

@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 focuses on enhancing the performance and reliability of the application's sitemap generation. By transitioning from sequential data fetching to a concurrent approach, the system can now process information more efficiently, leading to faster build times. Additionally, the inclusion of error handling mechanisms ensures that the sitemap generation remains robust even when encountering issues with external data sources.

Highlights

  • Performance Optimization: Refactored the sitemap generation logic in app/sitemap.ts to leverage Promise.all for concurrent fetching of speaker and talk data across different years. This significantly reduces I/O blocking and generation time.
  • Robustness and Error Handling: Implemented graceful error handling for API calls (getSpeakers and getTalks) during sitemap generation by adding .catch(() => []) to ensure that partial API failures do not crash the entire sitemap process.
  • Code Structure Improvement: Converted sequential await calls within loops to parallel execution, improving the efficiency and scalability of the sitemap generation process.
Changelog
  • app/sitemap.ts
    • Converted sequential data fetching for years, speakers, and talks into parallel operations using Promise.all.
    • Added error handling to getSpeakers and getTalks API calls to return empty arrays on failure, preventing sitemap generation crashes.
  • scorecard-badge.json
    • Ensured the file ends with a newline character.
  • scorecard-report.md
    • Adjusted table formatting for improved readability.
    • Removed extraneous blank lines throughout the document.
Ignored Files
  • Ignored by pattern: .github/workflows/** (1)
    • .github/workflows/scorecard.yml
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 8, 2026

Warning

Rate limit exceeded

@anyulled has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 15 minutes and 37 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 46b92074-0a93-4d76-8796-14cd263c74a3

📥 Commits

Reviewing files that changed from the base of the PR and between 625fd5c and aae0201.

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

Walkthrough

This pull request contains two independent changes: a Cypress test selector update and the removal of a minimatch dependency override. The first modifies which DOM elements are targeted in the homepage editions test, while the second eliminates an explicit version constraint from the dependency overrides configuration.

Changes

Cohort / File(s) Summary
Test Selector Update
cypress/e2e/home/home-editions.cy.ts
Changed DOM selector from targeting h5 elements to targeting elements with class hero8-header__event-line within the .hero8-header container.
Dependency Override Removal
package.json
Removed the minimatch override entry (^10.0.1) from the overrides section, allowing transitive dependency resolution to proceed without this explicit constraint.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐰 With whiskers twitching and nose held high,
I've updated selectors reaching for the sky,
Removed constraints that held minimatch tight,
Now dependencies flow free and light!
These changes small make testing right ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title refers to sitemap optimization using Promise.all, but the actual changeset only contains a Cypress test selector update and a minimatch dependency removal. The described sitemap optimization is absent. Update the title to reflect the actual changes: 'Update Cypress selector for hero header and remove minimatch override' or similar. The sitemap optimization should be the subject of a separate PR.
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 (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch bolt/optimize-sitemap-parallel-fetch-14425354385331826874

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.

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 effectively optimizes the sitemap generation by parallelizing data fetching for each year using Promise.all. This is a great improvement for performance. My review includes a suggestion to further refine the implementation by adopting a more functional pattern that avoids mutating shared state from within concurrent operations, which enhances code robustness and maintainability.

Comment on lines +32 to +88
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: 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) {
yearUrls.push({
url: `${baseUrl}/${year}/${page}`,
lastModified: new Date(),
changeFrequency: "weekly",
priority: 0.8,
});
}

// Fetch speakers and talks in parallel for each year, gracefully handling failures
const [speakers, sessionGroups] = await Promise.all([getSpeakers(year).catch(() => []), getTalks(year).catch(() => [])]);

const sessionGroups = await getTalks(year);
for (const group of sessionGroups) {
for (const talk of group.sessions) {
urls.push({
url: `${baseUrl}/${year}/talks/${talk.id}`,
for (const speaker of speakers) {
yearUrls.push({
url: `${baseUrl}/${year}/speakers/${speaker.id}`,
lastModified: new Date(),
changeFrequency: "weekly",
priority: 0.7,
});
}
}

const companies = getJobOffersByYear(year);
for (const company of companies) {
urls.push({
url: `${baseUrl}/${year}/job-offers/${slugify(company.name)}`,
lastModified: new Date(),
changeFrequency: "monthly",
priority: 0.5,
});
}
}
for (const group of sessionGroups) {
for (const talk of group.sessions) {
yearUrls.push({
url: `${baseUrl}/${year}/talks/${talk.id}`,
lastModified: new Date(),
changeFrequency: "weekly",
priority: 0.7,
});
}
}

const companies = getJobOffersByYear(year);
for (const company of companies) {
yearUrls.push({
url: `${baseUrl}/${year}/job-offers/${slugify(company.name)}`,
lastModified: new Date(),
changeFrequency: "monthly",
priority: 0.5,
});
}

urls.push(...yearUrls);
})
);
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

While the current implementation with urls.push(...yearUrls) inside the map callback will likely work correctly in Node.js's single-threaded event loop model, it relies on mutating a shared array from within concurrent asynchronous operations. This is generally considered an unsafe pattern that can be fragile and harder to reason about.

A more robust and functional approach is to have each asynchronous operation return its result, and then aggregate the results once all promises have been resolved. This avoids shared mutable state and makes the data flow more explicit.

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

      yearUrls.push({
        url: `${baseUrl}/${year}`,
        lastModified: new Date(),
        changeFrequency: "daily",
        priority: 0.9,
      });

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

      // Fetch speakers and talks in parallel for each year, gracefully handling failures
      const [speakers, sessionGroups] = await Promise.all([getSpeakers(year).catch(() => []), getTalks(year).catch(() => [])]);

      for (const speaker of speakers) {
        yearUrls.push({
          url: `${baseUrl}/${year}/speakers/${speaker.id}`,
          lastModified: new Date(),
          changeFrequency: "weekly",
          priority: 0.7,
        });
      }

      for (const group of sessionGroups) {
        for (const talk of group.sessions) {
          yearUrls.push({
            url: `${baseUrl}/${year}/talks/${talk.id}`,
            lastModified: new Date(),
            changeFrequency: "weekly",
            priority: 0.7,
          });
        }
      }

      const companies = getJobOffersByYear(year);
      for (const company of companies) {
        yearUrls.push({
          url: `${baseUrl}/${year}/job-offers/${slugify(company.name)}`,
          lastModified: new Date(),
          changeFrequency: "monthly",
          priority: 0.5,
        });
      }

      return yearUrls;
    })
  );
  urls.push(...yearlyUrls.flat());

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

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

32-87: Keep sitemap ordering deterministic while parallelizing by year.

urls.push(...yearUrls) from inside the parallel callbacks makes the final array order depend on whichever year's fetch finishes first. The data is still correct, but the sitemap output becomes unstable across runs and can create noisy diffs/cache churn. Return each yearUrls array from Promise.all and flatten afterward to preserve years order.

♻️ Suggested refactor
-  await Promise.all(
+  const urlsByYear = await Promise.all(
     years.map(async (year) => {
       const yearUrls: MetadataRoute.Sitemap = [];
       ...
-      urls.push(...yearUrls);
+      return yearUrls;
     })
   );
+
+  urls.push(...urlsByYear.flat());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/sitemap.ts` around lines 32 - 87, The current parallel map pushes
yearUrls into the shared urls array from inside each async callback, making
final sitemap order nondeterministic; instead have the async callback (the
years.map async function that calls getSpeakers and getTalks) return its
yearUrls array, await Promise.all to get an array of yearUrls results in the
original years order, then flatten/concat those results into urls (e.g., const
perYear = await Promise.all(...); urls.push(...perYear.flat());) so ordering is
stable while still parallelizing fetches like getSpeakers and getTalks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/scorecard.yml:
- Around line 12-16: The workflow pins third-party actions using mutable tags
(actions/checkout@v6 and markmishaev76/ai-harness-scorecard@v1); replace each
tag with the corresponding full commit SHA by looking up the exact commit in the
action repos and updating the uses entries to the full 40-character commit SHA
for markmishaev76/ai-harness-scorecard and for actions/checkout (or at minimum
for the third-party action) so the workflow references an immutable commit;
ensure you commit the updated workflow to the repo.

---

Nitpick comments:
In `@app/sitemap.ts`:
- Around line 32-87: The current parallel map pushes yearUrls into the shared
urls array from inside each async callback, making final sitemap order
nondeterministic; instead have the async callback (the years.map async function
that calls getSpeakers and getTalks) return its yearUrls array, await
Promise.all to get an array of yearUrls results in the original years order,
then flatten/concat those results into urls (e.g., const perYear = await
Promise.all(...); urls.push(...perYear.flat());) so ordering is stable while
still parallelizing fetches like getSpeakers and getTalks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ffa62f18-1e8d-4e20-a7a9-e64af587708d

📥 Commits

Reviewing files that changed from the base of the PR and between fca6a1c and a633d64.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • .github/workflows/scorecard.yml
  • app/sitemap.ts
  • cypress/e2e/home/home-editions.cy.ts
  • scorecard-badge.json
  • scorecard-report.md

Comment on lines +12 to +16
permissions:
contents: write
steps:
- uses: actions/checkout@v6
- uses: markmishaev76/ai-harness-scorecard@v1
Copy link

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:

# First, locate and inspect the scorecard.yml file
find . -name "scorecard.yml" -o -name "*scorecard*" | grep -E "\.(yml|yaml)$" | head -20

Repository: anyulled/devbcn-nextjs

Length of output: 99


🏁 Script executed:

# Check if .github/workflows/scorecard.yml exists and read it
if [ -f .github/workflows/scorecard.yml ]; then
  cat -n .github/workflows/scorecard.yml
else
  echo "File not found at .github/workflows/scorecard.yml"
  # Try to find it in the repo
  find . -name "*scorecard*.yml" -o -name "*scorecard*.yaml"
fi

Repository: anyulled/devbcn-nextjs

Length of output: 894


🌐 Web query:

GitHub Actions security hardening pin third-party actions commit SHA

💡 Result:

To harden GitHub Actions against supply‑chain attacks, pin any third‑party action (and reusable workflow) to an immutable reference—preferably a full commit SHA rather than a mutable tag like @v1 or a branch like @main. GitHub explicitly notes that pinning to a full‑length commit SHA is currently the only way to use an action as an immutable release. [1]

Why pin to a commit SHA (vs tags)

  • Tags and branches can move (retagging/force-push), so uses: owner/action@v1 may execute different code later without changing your workflow file. GitHub recommends SHA pinning to reduce impact of compromised dependencies. [1][2]
  • SHA pinning helps prevent surprise changes and reduces risk from malicious tag updates. [3]

How to pin (example)

Instead of:

- uses: actions/checkout@v4

Use the exact commit:

- uses: actions/checkout@<FULL_LENGTH_COMMIT_SHA>

(Replace with the commit that corresponds to the release you reviewed.) [1][3]

Enforce SHA pinning

  • GitHub provides repository/org/enterprise policies to require actions be pinned to a full-length commit SHA. [1]
  • You can also add a workflow check that fails PRs if actions aren’t SHA‑pinned (example: “Ensure SHA Pinned Actions”). [4]

Keeping pinned SHAs updated safely

  • Treat action updates like dependencies: periodically bump the pinned SHA to the next vetted release (often via Dependabot/automation + code review). This preserves immutability while still getting fixes. GitHub’s governance features and guidance emphasize proactive pinning and controlled updates. [2]

Sources

  1. GitHub Docs — Security hardening for GitHub Actions (pinning to full-length commit SHA; policies) https://docs.github.com/en/enterprise-cloud@latest/actions/how-tos/security-for-github-actions/security-guides/security-hardening-for-github-actions
  2. GitHub Changelog (2025‑08‑15) — Actions policy now supports blocking and SHA pinning actions https://github.blog/changelog/2025-08-15-github-actions-policy-now-supports-blocking-and-sha-pinning-actions/
  3. GitHub Well‑Architected — Actions security recommendations (pin to immutable reference; commit SHA example) https://wellarchitected.github.com/library/application-security/recommendations/actions-security/
  4. GitHub Marketplace — Ensure SHA Pinned Actions https://github.com/marketplace/actions/ensure-sha-pinned-actions

Pin the third-party action to an immutable commit SHA.

This workflow grants contents: write permission to a third-party action and then commits and pushes back to the repository. GitHub's security guidance recommends pinning all third-party actions to a full-length commit SHA because tags like @v1 are mutable and can be retagged; a compromised action could exploit write access to introduce malicious code into your repository. Commit SHAs are the only immutable reference for actions.

🔒 Suggested hardening
-      - uses: markmishaev76/ai-harness-scorecard@v1
+      - uses: markmishaev76/ai-harness-scorecard@<full-length-commit-sha> # v1
         id: scorecard
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/scorecard.yml around lines 12 - 16, The workflow pins
third-party actions using mutable tags (actions/checkout@v6 and
markmishaev76/ai-harness-scorecard@v1); replace each tag with the corresponding
full commit SHA by looking up the exact commit in the action repos and updating
the uses entries to the full 40-character commit SHA for
markmishaev76/ai-harness-scorecard and for actions/checkout (or at minimum for
the third-party action) so the workflow references an immutable commit; ensure
you commit the updated workflow to the repo.

…lock.json

Co-authored-by: anyulled <100741+anyulled@users.noreply.github.com>
Co-authored-by: anyulled <100741+anyulled@users.noreply.github.com>
Co-authored-by: anyulled <100741+anyulled@users.noreply.github.com>
Co-authored-by: anyulled <100741+anyulled@users.noreply.github.com>
Co-authored-by: anyulled <100741+anyulled@users.noreply.github.com>
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