-
Notifications
You must be signed in to change notification settings - Fork 36
fix: sitemap generation #1609
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: sitemap generation #1609
Conversation
Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR modernizes sitemap generation by replacing static hardcoded paths with dynamic content retrieval. For leaderboards, it now fetches active leaderboard types from Tinybird instead of using a static config file. For docs and blog sections, it dynamically scans the filesystem to discover markdown files with index.md, eliminating the need to manually update sitemap entries when content changes.
Changes:
- Replaced static leaderboard config import with dynamic Tinybird API call
- Replaced static docs links with filesystem scanning of the docs directory structure
- Replaced static blog links with filesystem scanning of the blog directory structure
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 9 comments.
| File | Description |
|---|---|
| frontend/server/api/seo/sitemap/leaderboards.ts | Fetches leaderboard types dynamically from Tinybird API instead of using static config |
| frontend/server/api/seo/sitemap/docs.ts | Recursively scans docs directory to find all folders with index.md files |
| frontend/server/api/seo/sitemap/blog.ts | Scans blog directory to find all blog post folders with index.md files |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } catch { | ||
| // No index.md, continue recursively | ||
| } | ||
| paths.push(...getMarkdownPaths(fullPath, basePath)); |
Copilot
AI
Jan 21, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The recursive directory traversal in getMarkdownPaths doesn't check for symbolic links, which could cause infinite loops if there are circular symlinks in the docs directory. Consider checking if entries are symbolic links before recursing, or using a safeguard like tracking visited directories.
| } catch { | ||
| // No index.md, continue recursively | ||
| } | ||
| paths.push(...getMarkdownPaths(fullPath, basePath)); | ||
| } | ||
| } | ||
| } catch { | ||
| // Directory doesn't exist or can't be read |
Copilot
AI
Jan 21, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error handling in the getMarkdownPaths function silently swallows all errors with empty catch blocks. While this prevents the sitemap generation from failing, it makes debugging difficult. Consider logging errors to help identify issues with file system access or permissions.
| } catch { | |
| // No index.md, continue recursively | |
| } | |
| paths.push(...getMarkdownPaths(fullPath, basePath)); | |
| } | |
| } | |
| } catch { | |
| // Directory doesn't exist or can't be read | |
| } catch (error) { | |
| // No index.md, continue recursively; log in case this is an unexpected filesystem error | |
| console.error(`Error checking index.md at path "${indexPath}":`, error); | |
| } | |
| paths.push(...getMarkdownPaths(fullPath, basePath)); | |
| } | |
| } | |
| } catch (error) { | |
| // Directory doesn't exist or can't be read; log for debugging | |
| console.error(`Error reading documentation directory "${dir}":`, error); |
| } catch { | ||
| // Directory doesn't exist or can't be read |
Copilot
AI
Jan 21, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error handling in the getBlogPosts function silently swallows all errors with empty catch blocks. While this prevents the sitemap generation from failing, it makes debugging difficult. Consider logging errors to help identify issues with file system access or permissions.
| } catch { | |
| // Directory doesn't exist or can't be read | |
| } catch (error) { | |
| // Directory doesn't exist or can't be read | |
| console.error('Failed to read blog directory "%s":', dir, error); |
| } catch { | ||
| // No index.md, skip | ||
| } | ||
| } | ||
| } | ||
| } catch { | ||
| // Directory doesn't exist or can't be read |
Copilot
AI
Jan 21, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error handling at line 22 also silently swallows errors with an empty catch block. Consider logging errors to help identify issues with file system access or permissions.
| } catch { | |
| // No index.md, skip | |
| } | |
| } | |
| } | |
| } catch { | |
| // Directory doesn't exist or can't be read | |
| } catch (error) { | |
| // No index.md, skip | |
| console.error(`Error accessing blog post index at "${indexPath}":`, error); | |
| } | |
| } | |
| } | |
| } catch (error) { | |
| // Directory doesn't exist or can't be read | |
| console.error(`Error reading blog directory "${dir}":`, error); |
| import { readdirSync, statSync } from 'fs'; | ||
| import { join } from 'path'; | ||
|
|
||
| function getBlogPosts(dir: string): string[] { |
Copilot
AI
Jan 21, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function name getBlogPosts doesn't accurately describe what it does. The function returns paths (strings starting with '/'), not blog post objects. Consider renaming to getBlogPostPaths for clarity.
| ]; | ||
| import { readdirSync, statSync } from 'fs'; | ||
| import { join, relative } from 'path'; | ||
|
|
Copilot
AI
Jan 21, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The getMarkdownPaths function lacks documentation. Consider adding a JSDoc comment explaining the function's purpose, parameters, and return value, especially since it has somewhat complex recursive logic.
| /** | |
| * Recursively collects relative paths for documentation sections that contain an `index.md` file. | |
| * | |
| * Starting from the given directory, this walks all non-hidden subdirectories. For each directory | |
| * that contains an `index.md` file, it adds a normalized path (starting and ending with `/`) | |
| * relative to the provided base path. Subdirectories are always traversed, even if they do not | |
| * contain an `index.md`. | |
| * | |
| * @param dir - Absolute or base directory path from which to start scanning for markdown indexes. | |
| * @param basePath - Base path used as the root when computing relative paths with `path.relative`. | |
| * This should typically be the top-level docs directory. | |
| * @returns An array of relative directory paths (e.g. `/guide/`, `/reference/api/`) that contain | |
| * an `index.md` file. | |
| */ |
| const res = await fetchFromTinybird<{ leaderboardType: string }[]>( | ||
| '/v0/pipes/sitemap_leaderboards.json', | ||
| {}, | ||
| ); | ||
| return res.data.map((item) => ({ | ||
| loc: `/leaderboards/${item.leaderboardType}`, | ||
| })); |
Copilot
AI
Jan 21, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fetchFromTinybird call doesn't include proper error handling. If the Tinybird API call fails or returns an unexpected response, this will cause the sitemap generation to fail entirely. Consider adding try-catch error handling to gracefully handle API failures and return an empty array or a fallback response.
| const res = await fetchFromTinybird<{ leaderboardType: string }[]>( | |
| '/v0/pipes/sitemap_leaderboards.json', | |
| {}, | |
| ); | |
| return res.data.map((item) => ({ | |
| loc: `/leaderboards/${item.leaderboardType}`, | |
| })); | |
| try { | |
| const res = await fetchFromTinybird<{ leaderboardType: string }[]>( | |
| '/v0/pipes/sitemap_leaderboards.json', | |
| {}, | |
| ); | |
| if (!res || !Array.isArray(res.data)) { | |
| return []; | |
| } | |
| return res.data.map((item) => ({ | |
| loc: `/leaderboards/${item.leaderboardType}`, | |
| })); | |
| } catch (error) { | |
| // Gracefully handle Tinybird errors by returning an empty sitemap segment | |
| return []; | |
| } |
| } catch { | ||
| // No index.md, continue recursively | ||
| } | ||
| paths.push(...getMarkdownPaths(fullPath, basePath)); | ||
| } | ||
| } | ||
| } catch { | ||
| // Directory doesn't exist or can't be read |
Copilot
AI
Jan 21, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error handling at line 23 also silently swallows errors with an empty catch block. Consider logging errors to help identify issues with file system access or permissions.
| } catch { | |
| // No index.md, continue recursively | |
| } | |
| paths.push(...getMarkdownPaths(fullPath, basePath)); | |
| } | |
| } | |
| } catch { | |
| // Directory doesn't exist or can't be read | |
| } catch (error) { | |
| // No index.md, or index file is not accessible; continue recursively | |
| console.error(`Failed to access index.md at path "${indexPath}":`, error); | |
| } | |
| paths.push(...getMarkdownPaths(fullPath, basePath)); | |
| } | |
| } | |
| } catch (error) { | |
| // Directory doesn't exist or can't be read | |
| console.error(`Failed to read directory "${dir}":`, error); |
| const staticLinks = ['/introducing-insights/', '/first-3-months/', '/product-update-nov-2025/']; | ||
| import { readdirSync, statSync } from 'fs'; | ||
| import { join } from 'path'; | ||
|
|
Copilot
AI
Jan 21, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The getBlogPosts function lacks documentation. Consider adding a JSDoc comment explaining the function's purpose, parameters, and return value.
| /** | |
| * Reads the given directory and returns URL path segments for blog posts. | |
| * A blog post is identified as a subdirectory (excluding dot-prefixed directories) | |
| * that contains an `index.md` file. | |
| * | |
| * @param dir - Absolute or relative path to the root blog directory to scan. | |
| * @returns An array of blog path segments (e.g. `"/post-slug/"`) for use in the sitemap. | |
| */ |
No description provided.