feat: migrate blog from external API to MDX file-based content (#407)#409
feat: migrate blog from external API to MDX file-based content (#407)#409akintewe wants to merge 6 commits intoboundlessfi:mainfrom
Conversation
…lessfi#407) Replace API-fetched blog posts with statically generated MDX content. Blog pages now use server components and next-mdx-remote for SSG, eliminating loading states, improving SEO, and removing the backend dependency. - Add lib/mdx.ts with getAllBlogPosts, getBlogPostBySlug, getRelatedPosts - Add content/blog/ with 3 sample MDX posts (frontmatter-driven) - Convert blog listing and detail pages to async server components - Replace useMarkdown hook + inline CSS with Tailwind Typography (prose) - Update BlogCard, BlogGrid, BlogSection, StreamingBlogGrid to MdxBlogPost type - Update blog search API route and sitemap to use MDX data source - Delete lib/api/blog.ts (replaced by lib/mdx.ts) - Add next-mdx-remote, gray-matter, @tailwindcss/typography dependencies
|
@akintewe is attempting to deploy a commit to the Threadflow Team on Vercel. A member of the Team first needs to authorize it. |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughReplaces the API-backed blog with a file-based MDX system: removes legacy blog API, adds Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Client (Browser)
participant Page as Next.js Page
participant MDXlib as lib/mdx
participant FS as FileSystem (content/blog)
Client->>Page: Request /blog/[slug]
Page->>MDXlib: getBlogPostBySlug(slug)
MDXlib->>FS: read `content/blog/{slug}.mdx`
FS-->>MDXlib: raw MDX + frontmatter
MDXlib-->>Page: MdxBlogPostWithContent (compiled React content)
Page->>MDXlib: getRelatedPosts(slug, tags, categories)
MDXlib-->>Page: related posts list
Page-->>Client: Rendered HTML with compiled MDX and related posts
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 3❌ Failed checks (3 warnings)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
components/landing-page/blog/BlogGrid.tsx (2)
98-130:⚠️ Potential issue | 🟠 MajorLoad-more still depends on
/api/blog/postsdespite static MDX pagination.This handler reintroduces runtime API calls and fallback delays even though posts are already in-memory/static. It adds avoidable latency and brittle failure paths.
♻️ Suggested simplification (client-only pagination)
- const handleLoadMore = useCallback(async () => { - if (isLoading || !hasMorePosts) return; - - setIsLoading(true); - - try { - // Simulate streaming by loading more posts - const nextPage = currentPage + 1; - const response = await fetch(`/api/blog/posts?page=${nextPage}&limit=12`); - - if (response.ok) { - const data = await response.json(); - setAllPosts(prev => [...prev, ...data.posts]); - setCurrentPage(nextPage); - setHasMorePosts(data.hasMore); - setVisiblePosts(prev => prev + 12); - } else { - // Fallback to local loading if API fails - setTimeout(() => { - setVisiblePosts(prev => prev + 12); - setIsLoading(false); - }, 500); - } - } catch { - // Fallback to local loading if API fails - setTimeout(() => { - setVisiblePosts(prev => prev + 12); - setIsLoading(false); - }, 500); - } finally { - setIsLoading(false); - } - }, [isLoading, hasMorePosts, currentPage]); + const handleLoadMore = useCallback(() => { + if (isLoading || !hasMorePostsToShow) return; + setIsLoading(true); + setVisiblePosts(prev => prev + 12); + setCurrentPage(prev => prev + 1); + setIsLoading(false); + }, [isLoading, hasMorePostsToShow]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/landing-page/blog/BlogGrid.tsx` around lines 98 - 130, The load-more handler handleLoadMore should not call /api/blog/posts or use fallbacks; replace the fetch logic with client-side pagination using the already-loaded allPosts array: compute nextVisible = Math.min(allPosts.length, visiblePosts + 12), setVisiblePosts(nextVisible), update currentPage (if you need pages) and hasMorePosts = nextVisible < allPosts.length, and remove the try/catch/fetch and artificial setTimeout paths; keep setIsLoading(true) at start and ensure you setIsLoading(false) only once after updating state (or remove loading state entirely if instant).
146-155:⚠️ Potential issue | 🔴 Critical
handleCardClicknever navigates to the article.Line 151 only logs, and the callback never calls router navigation. On the blog grid, “Continue Reading” won’t open the post.
🔧 Proposed fix
import React, { useState, useCallback, useMemo } from 'react'; +import { useRouter } from 'next/navigation'; @@ const BlogGrid: React.FC<BlogGridProps> = ({ @@ }) => { + const router = useRouter(); @@ const handleCardClick = useCallback((slug: string) => { setIsNavigating(true); - // The navigation will be handled by Next.js Link, but we show loading state - // The loading state will be cleared when the page actually navigates - // eslint-disable-next-line no-console - console.log(`Navigating to blog post: ${slug}`); - setTimeout(() => { - setIsNavigating(false); - }, 2000); // Fallback timeout - }, []); + router.push(`/blog/${slug}`); + }, [router]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/landing-page/blog/BlogGrid.tsx` around lines 146 - 155, handleCardClick currently only logs and never triggers navigation; fix it by importing and using Next.js useRouter inside the component, capture router (const router = useRouter()), and update handleCardClick to call router.push(slug) (or the correct blog path) after setIsNavigating(true). Also subscribe to router.events (routeChangeComplete and routeChangeError) to clear setIsNavigating(false) and remove those listeners in cleanup to avoid memory leaks; keep the existing fallback setTimeout as secondary. Ensure you reference handleCardClick, setIsNavigating, router.push and router.events/routeChangeComplete in your changes.components/landing-page/blog/BlogPostDetails.tsx (1)
28-35:⚠️ Potential issue | 🟠 MajorAvoid timezone drift when formatting
publishedAt.
new Date('YYYY-MM-DD')parses as UTC per ECMAScript spec, but when displayed viatoLocaleDateString()without explicit timezone handling, it renders in the user's local timezone and can appear as the previous day in negative UTC offsets (e.g., US timezones).💡 Suggested fix
const formatDate = (dateString: string) => { - const date = new Date(dateString); + const date = new Date(`${dateString}T00:00:00Z`); return date.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric', + timeZone: 'UTC', }); };Also applies to: 121
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/landing-page/blog/BlogPostDetails.tsx` around lines 28 - 35, The formatDate function can suffer timezone drift because new Date(dateString) treats 'YYYY-MM-DD' as UTC and toLocaleDateString renders in the user's local zone; update formatDate (and any other uses like the one at line ~121) to parse the date string into local date components and construct the Date via new Date(year, monthIndex, day) (or otherwise normalize to UTC before formatting) so the displayed date is stable across timezones; modify the formatDate helper to extract year/month/day from the input string and build the Date that toLocaleDateString will format consistently.app/(landing)/blog/[slug]/page.tsx (1)
1-67:⚠️ Potential issue | 🟡 MinorCI is blocked by Prettier formatting in this file.
The pipeline output indicates formatting violations; this should be prettified before merge.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(landing)/blog/[slug]/page.tsx around lines 1 - 67, Prettier-formatting failures in this file are blocking CI; run the repository's formatter (e.g., npm run format or npx prettier --write) on this file and reformat the exports and function blocks (including generateStaticParams, generateMetadata, and the BlogPostPage component) so spacing, commas, quotes and JSX formatting conform to the project's Prettier config, then re-run lint/format checks and commit the updated file.
🧹 Nitpick comments (2)
components/landing-page/blog/StreamingBlogGrid.tsx (1)
28-29: Avoid freezinginitialPostsin local state when it never mutates.Line 28 stores
initialPostsin state without using a setter, which can produce stale data if props ever update. A plain constant is safer and clearer.♻️ Suggested change
- const [allPosts] = useState<MdxBlogPost[]>(initialPosts); + const allPosts = initialPosts;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/landing-page/blog/StreamingBlogGrid.tsx` around lines 28 - 29, The component StreamingBlogGrid currently does const [allPosts] = useState<MdxBlogPost[]>(initialPosts), which freezes initialPosts into state and can become stale if props change; replace that useState usage with a plain constant (e.g., const allPosts = initialPosts) and remove the unused setter reference so the component reads the latest initialPosts prop, keeping the existing useState for visibleCount (setVisibleCount) unchanged.app/(landing)/blog/[slug]/page.tsx (1)
23-24: Avoid compiling MDX in metadata and again during page render.
getBlogPostBySlugcompiles MDX content. Calling it in both Line 23 and Line 56 doubles expensive work; metadata only needs frontmatter fields.⚡ Suggested optimization
export async function generateMetadata({ params, }: BlogPostPageProps): Promise<Metadata> { const { slug } = await params; - const post = await getBlogPostBySlug(slug); + const post = getAllBlogPosts().find(p => p.slug === slug); if (!post) { return { title: 'Blog Post Not Found | Boundless', @@ const BlogPostPage = async ({ params }: BlogPostPageProps) => { const { slug } = await params; const post = await getBlogPostBySlug(slug);Also applies to: 56-57
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(landing)/blog/[slug]/page.tsx around lines 23 - 24, The metadata generation currently calls getBlogPostBySlug(slug) which compiles MDX and causes duplicate work when the page render also calls getBlogPostBySlug; change metadata to fetch only frontmatter (either by adding a new helper like getBlogPostFrontmatterBySlug(slug) or by adding a skipCompilation option to getBlogPostBySlug) and use that lightweight call in the metadata block, leaving the full getBlogPostBySlug(slug) (which compiles MDX) only for the page render (e.g., in the default export function); update call sites to use the new flag/helper so MDX compilation happens once only during render.
🤖 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/api/blog/search/route.ts`:
- Around line 33-35: The tag filter is currently case/whitespace sensitive and
can miss matches; update the filtering to normalize both the incoming query tags
and the post tags before exact matching: parse/split any comma-separated values
in the incoming tags, map the resulting tags to trimmed, lowercased strings
(e.g., normalizedTags), and when filtering filteredPosts use those
normalizedTags to check against post.tags normalized via trimming and
toLowerCase (referencing filteredPosts, tags, and post.tags in route.ts).
In `@components/landing-page/blog/StreamingBlogGrid.tsx`:
- Around line 301-303: The “You’ve reached the end of the blog posts!” message
is shown during filtered/search views because the render only checks
hasMoreToShow and filteredPosts; update the conditional to also ensure no active
filters/search are applied (e.g. add a negated flag like !isFiltering or
!hasActiveFilters or check that filteredPosts length equals the full posts
length). Locate the render that uses hasMoreToShow and filteredPosts and change
the condition to something like: show message only when hasMoreToShow is false,
filteredPosts.length > 0, and there are no active filters/search (use the
existing filter/search state used when computing filteredPosts or add an
isFiltering helper if needed).
In `@content/blog/building-on-stellar.mdx`:
- Around line 1-99: The MDX file fails Prettier; run the project's
Prettier/formatter on this MDX content and commit the changes so CI passes —
specifically format the frontmatter and markdown sections and ensure the Rust
code block (the release_milestone function) is properly fenced and indented,
remove any trailing whitespace, and preserve existing content; run the same
formatter command used by CI (e.g., npm/yarn format or prettier --write) and
re-run linting before pushing.
In `@content/blog/milestone-based-funding.mdx`:
- Line 1: The file content/blog/milestone-based-funding.mdx currently fails
Prettier (the leading '---'/frontmatter line is not formatted); fix by running
your project's formatter (e.g., run prettier --write on
content/blog/milestone-based-funding.mdx) or apply the repo's Prettier rules to
the file (ensure proper frontmatter syntax and a trailing newline), then
re-commit the formatted file so CI stops reporting the Prettier error.
In `@lib/mdx.ts`:
- Around line 1-122: The file fails Prettier formatting; run the project's
Prettier formatter over this file and commit the changes so CI passes.
Specifically, format lib/mdx.ts (containing parseFrontmatter, getAllBlogPosts,
getBlogPostBySlug, getRelatedPosts) using the repo's Prettier config (e.g., npx
prettier --write lib/mdx.ts or your editor's format command) and ensure the
resulting file is lint/format clean before pushing.
- Around line 34-48: The return object in lib/mdx.ts must harden frontmatter
normalization: validate and sanitize categories and tags to ensure elements are
strings (e.g., replace the current Array.isArray(data.categories) ?
data.categories : [] logic with filtering/mapping so only typeof item ===
'string' are returned), and similarly sanitize author.name and author.image
(coerce to string or default to ''), plus ensure readingTime and isFeatured are
coerced to number/boolean respectively; update the object construction that
produces slug, title, excerpt, coverImage, author, categories, tags,
readingTime, and isFeatured so downstream callers (e.g., code that calls
tag.toLowerCase()) cannot receive non-string items.
---
Outside diff comments:
In `@app/`(landing)/blog/[slug]/page.tsx:
- Around line 1-67: Prettier-formatting failures in this file are blocking CI;
run the repository's formatter (e.g., npm run format or npx prettier --write) on
this file and reformat the exports and function blocks (including
generateStaticParams, generateMetadata, and the BlogPostPage component) so
spacing, commas, quotes and JSX formatting conform to the project's Prettier
config, then re-run lint/format checks and commit the updated file.
In `@components/landing-page/blog/BlogGrid.tsx`:
- Around line 98-130: The load-more handler handleLoadMore should not call
/api/blog/posts or use fallbacks; replace the fetch logic with client-side
pagination using the already-loaded allPosts array: compute nextVisible =
Math.min(allPosts.length, visiblePosts + 12), setVisiblePosts(nextVisible),
update currentPage (if you need pages) and hasMorePosts = nextVisible <
allPosts.length, and remove the try/catch/fetch and artificial setTimeout paths;
keep setIsLoading(true) at start and ensure you setIsLoading(false) only once
after updating state (or remove loading state entirely if instant).
- Around line 146-155: handleCardClick currently only logs and never triggers
navigation; fix it by importing and using Next.js useRouter inside the
component, capture router (const router = useRouter()), and update
handleCardClick to call router.push(slug) (or the correct blog path) after
setIsNavigating(true). Also subscribe to router.events (routeChangeComplete and
routeChangeError) to clear setIsNavigating(false) and remove those listeners in
cleanup to avoid memory leaks; keep the existing fallback setTimeout as
secondary. Ensure you reference handleCardClick, setIsNavigating, router.push
and router.events/routeChangeComplete in your changes.
In `@components/landing-page/blog/BlogPostDetails.tsx`:
- Around line 28-35: The formatDate function can suffer timezone drift because
new Date(dateString) treats 'YYYY-MM-DD' as UTC and toLocaleDateString renders
in the user's local zone; update formatDate (and any other uses like the one at
line ~121) to parse the date string into local date components and construct the
Date via new Date(year, monthIndex, day) (or otherwise normalize to UTC before
formatting) so the displayed date is stable across timezones; modify the
formatDate helper to extract year/month/day from the input string and build the
Date that toLocaleDateString will format consistently.
---
Nitpick comments:
In `@app/`(landing)/blog/[slug]/page.tsx:
- Around line 23-24: The metadata generation currently calls
getBlogPostBySlug(slug) which compiles MDX and causes duplicate work when the
page render also calls getBlogPostBySlug; change metadata to fetch only
frontmatter (either by adding a new helper like
getBlogPostFrontmatterBySlug(slug) or by adding a skipCompilation option to
getBlogPostBySlug) and use that lightweight call in the metadata block, leaving
the full getBlogPostBySlug(slug) (which compiles MDX) only for the page render
(e.g., in the default export function); update call sites to use the new
flag/helper so MDX compilation happens once only during render.
In `@components/landing-page/blog/StreamingBlogGrid.tsx`:
- Around line 28-29: The component StreamingBlogGrid currently does const
[allPosts] = useState<MdxBlogPost[]>(initialPosts), which freezes initialPosts
into state and can become stale if props change; replace that useState usage
with a plain constant (e.g., const allPosts = initialPosts) and remove the
unused setter reference so the component reads the latest initialPosts prop,
keeping the existing useState for visibleCount (setVisibleCount) unchanged.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (17)
app/(landing)/blog/[slug]/page.tsxapp/(landing)/blog/page.tsxapp/api/blog/search/route.tsapp/globals.cssapp/sitemap.tscomponents/landing-page/blog/BlogCard.tsxcomponents/landing-page/blog/BlogGrid.tsxcomponents/landing-page/blog/BlogPostDetails.tsxcomponents/landing-page/blog/BlogSection.tsxcomponents/landing-page/blog/BlogSectionClient.tsxcomponents/landing-page/blog/StreamingBlogGrid.tsxcontent/blog/building-on-stellar.mdxcontent/blog/milestone-based-funding.mdxcontent/blog/welcome-to-boundless.mdxlib/api/blog.tslib/mdx.tspackage.json
💤 Files with no reviewable changes (1)
- lib/api/blog.ts
…idation - Fix security check to only fail on moderate/high/critical vulnerabilities, matching the --audit-level=moderate flag (24 pre-existing low-severity vulns in @creit.tech/stellar-wallets-kit transitive deps are not actionable) - Skip merge commits in commit message validation to prevent false failures - Fix Prettier formatting in MDX files, lib/mdx.ts, and blog slug page
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
components/landing-page/blog/BlogCard.tsx (1)
62-63:⚠️ Potential issue | 🟡 MinorRaw ISO date string will be displayed to users.
post.publishedAtis stored as'2025-01-15'(see frontmatter). Rendering it directly produces an unformatted date. Consider formatting it before display.✨ Suggested formatting
- {post.publishedAt} + {new Date(post.publishedAt).toLocaleDateString('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric', + })}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/landing-page/blog/BlogCard.tsx` around lines 62 - 63, BlogCard currently renders the raw ISO string post.publishedAt; update the component (BlogCard) to parse and format post.publishedAt before rendering (e.g., using Date constructor or a date library/Intl.DateTimeFormat) and replace the direct {post.publishedAt} output with the formatted string; ensure you handle missing or invalid dates (fallback string or omit) and consider extracting the formatting into a small helper like formatDate to keep the JSX clean.
♻️ Duplicate comments (1)
lib/mdx.ts (1)
44-45:⚠️ Potential issue | 🟠 MajorArray elements not validated as strings — same past review concern is unresolved.
data.categoriesanddata.tagsare accepted as-is if they are arrays, without filtering non-string elements. Downstream callers (e.g.,tag.toLowerCase()in search, andtags.includes(t)ingetRelatedPosts) will throw or misbehave on non-string entries.🛡️ Proposed fix (applies identically in both `parseFrontmatter` and `getBlogPostBySlug`)
- categories: Array.isArray(data.categories) ? data.categories : [], - tags: Array.isArray(data.tags) ? data.tags : [], - readingTime: data.readingTime ?? 0, + categories: Array.isArray(data.categories) + ? data.categories.filter((c): c is string => typeof c === 'string') + : [], + tags: Array.isArray(data.tags) + ? data.tags.filter((t): t is string => typeof t === 'string') + : [], + readingTime: typeof data.readingTime === 'number' ? data.readingTime : 0,Also applies to: 91-92
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/mdx.ts` around lines 44 - 45, Filter and sanitize data.categories and data.tags to include only string elements before assigning them (apply same change in parseFrontmatter and getBlogPostBySlug); the current code accepts any array and can break callers that call string methods like tag.toLowerCase() or tags.includes(t). Replace the direct assignment of data.categories/data.tags with a guarded expression that checks Array.isArray(...) and then filters to keep only typeof item === 'string' (optionally trimming each string) so downstream uses of categories/tags are safe.
🧹 Nitpick comments (6)
lib/mdx.ts (2)
51-65: Exported utility functions usefunctiondeclarations instead ofconstarrow functions.
getAllBlogPosts,getBlogPostBySlug, andgetRelatedPostsare all declared with thefunctionkeyword. As per coding guidelines, exported functions in.tsfiles should useconstarrow functions with explicit type annotations.♻️ Example for `getAllBlogPosts`
-export function getAllBlogPosts(): MdxBlogPost[] { +export const getAllBlogPosts = (): MdxBlogPost[] => {As per coding guidelines, "Prefer const arrow functions with explicit type annotations over function declarations."
Also applies to: 67-97, 99-120
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/mdx.ts` around lines 51 - 65, Convert the exported function declarations to const arrow functions with explicit TypeScript return and parameter types: replace the `function` declaration for getAllBlogPosts with `const getAllBlogPosts: () => MdxBlogPost[] = () => { ... }`, and do the same for getBlogPostBySlug and getRelatedPosts (e.g., include parameter types like `slug: string` and explicit return types). Keep existing logic, imports, and exported names identical, and ensure the functions remain exported (export const ...) so typings and linting conform to the project's guideline.
99-119:getRelatedPostsre-reads all MDX files viagetAllBlogPosts().When a page calls
getBlogPostBySlugand thengetRelatedPosts, every.mdxfile inBLOG_DIRis read and parsed twice. As the blog grows this becomes a measurable I/O overhead on every static render.Consider either accepting the post list as a parameter or memoising
getAllBlogPostswith a module-level cache (safe at build time / in RSC context):let postsCache: MdxBlogPost[] | null = null; export function getAllBlogPosts(): MdxBlogPost[] { if (postsCache) return postsCache; // ...existing logic... postsCache = posts.sort(...); return postsCache; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/mdx.ts` around lines 99 - 119, getRelatedPosts currently calls getAllBlogPosts() again causing all .mdx files to be reread; fix by avoiding duplicate I/O either by changing getRelatedPosts to accept the precomputed posts array (e.g., add an overload or new signature like getRelatedPosts(currentSlug, tags, categories, posts, limit)) or by memoizing getAllBlogPosts with a module-level cache (introduce postsCache: MdxBlogPost[] | null and return cached posts from getAllBlogPosts when set), and update callers (e.g., getBlogPostBySlug consumer) to pass the cached list or rely on the memoized getAllBlogPosts to prevent double-reading.app/(landing)/blog/[slug]/page.tsx (3)
50-61:BlogPostPagemissing explicit return type annotation.As per coding guidelines, const arrow functions should carry explicit type annotations.
♻️ Proposed fix
-const BlogPostPage = async ({ params }: BlogPostPageProps) => { +const BlogPostPage = async ({ params }: BlogPostPageProps): Promise<React.ReactElement> => {As per coding guidelines, "Prefer const arrow functions with explicit type annotations over function declarations."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(landing)/blog/[slug]/page.tsx around lines 50 - 61, The BlogPostPage arrow function lacks an explicit return type; update its signature to include a Promise return type (e.g., change "const BlogPostPage = async ({ params }: BlogPostPageProps) =>" to "const BlogPostPage = async ({ params }: BlogPostPageProps): Promise<JSX.Element> =>") so the async server component's return type is explicit; reference the BlogPostPage identifier and the BlogPostPageProps type when making this change.
11-12:generateStaticParamsis unnecessarilyasync.
getAllBlogPosts()is synchronous. Theasynckeyword adds overhead without benefit.♻️ Proposed fix
-export async function generateStaticParams() { +export function generateStaticParams() {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(landing)/blog/[slug]/page.tsx around lines 11 - 12, The generateStaticParams function is marked async but calls the synchronous getAllBlogPosts(), so remove the unnecessary async keyword from generateStaticParams (and any associated await usage or unnecessary Promise handling) so it returns the mapped slug objects synchronously; locate the generateStaticParams declaration and change it to a plain function that returns getAllBlogPosts().map(post => ({ slug: post.slug })).
19-19: WrapgetBlogPostBySlugwithReact.cache()to avoid duplicate MDX compilation during static generation.
getBlogPostBySlugis called in bothgenerateMetadata(line 19) andBlogPostPage(line 52), causingcompileMDXto run twice per blog post during static build. Since the function uses file I/O andcompileMDXrather thanfetch(), Next.js does not automatically deduplicate these calls within the same render pass.Wrap the function with React's
cache()at module level:Suggested fix
// lib/mdx.ts import { cache } from 'react'; export const getBlogPostBySlug = cache(async (slug: string): Promise<MdxBlogPostWithContent | null> => { // ...existing implementation });This ensures both
generateMetadataand the page component share a single result per request.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(landing)/blog/[slug]/page.tsx at line 19, Wrap the module-level function getBlogPostBySlug with React's cache() so calls from generateMetadata and BlogPostPage share a single cached result and avoid duplicate compileMDX/file I/O; update the exported getBlogPostBySlug definition in the mdx/lib file to be exported as cache(async (slug: string) => { ... }) while keeping the same signature and internal logic so generateMetadata and BlogPostPage continue calling getBlogPostBySlug unchanged.components/landing-page/blog/BlogCard.tsx (1)
87-91: Inline event handler should be extracted to a named handler.The
onClickuses an anonymous arrow function directly. As per coding guidelines, event handlers should start with the'handle'prefix (e.g.,handleClick).♻️ Proposed refactor
+const handleCardClick = (e: React.MouseEvent<HTMLButtonElement>) => { + e.preventDefault(); + onCardClick?.(post.slug); +}; <button - onClick={e => { - e.preventDefault(); - onCardClick?.(post.slug); - }} + onClick={handleCardClick}As per coding guidelines, "Event handlers should start with 'handle' prefix (e.g., handleClick, handleSubmit)."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/landing-page/blog/BlogCard.tsx` around lines 87 - 91, Extract the inline anonymous onClick handler into a named function called handleCardClick that accepts the event, calls e.preventDefault(), and then invokes onCardClick?.(post.slug); replace onClick={() => ...} with onClick={handleCardClick}. Declare handleCardClick inside the same component (preferably wrapped with useCallback to avoid recreating the function) and type its parameter as a React.MouseEvent<HTMLButtonElement> if using TypeScript.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@content/blog/welcome-to-boundless.mdx`:
- Around line 6-8: The frontmatter uses an external placeholder avatar in
author.image which introduces a runtime dependency and privacy/CSP risk; replace
the external URL (https://i.pravatar.cc/150?img=1) with a real, self-hosted
asset or a repository-relative image path (or remove the image field) in the
welcome-to-boundless.mdx frontmatter so author.image points to a locally hosted
file and author.name remains unchanged.
In `@lib/mdx.ts`:
- Around line 61-64: The comparator in posts.sort uses new Date(...).getTime()
which can be NaN for empty/malformed publishedAt values, producing undefined
ordering; change the comparator to normalize each timestamp (e.g., compute tA =
new Date(a.publishedAt).getTime(); if Number.isNaN(tA) set tA to a stable
fallback like 0 or Date.MIN_VALUE-equivalent, do the same for tB) and then
return tB - tA so posts with missing/invalid publishedAt sort consistently (use
the posts.sort call and publishedAt property names to locate and update the
comparator).
---
Outside diff comments:
In `@components/landing-page/blog/BlogCard.tsx`:
- Around line 62-63: BlogCard currently renders the raw ISO string
post.publishedAt; update the component (BlogCard) to parse and format
post.publishedAt before rendering (e.g., using Date constructor or a date
library/Intl.DateTimeFormat) and replace the direct {post.publishedAt} output
with the formatted string; ensure you handle missing or invalid dates (fallback
string or omit) and consider extracting the formatting into a small helper like
formatDate to keep the JSX clean.
---
Duplicate comments:
In `@lib/mdx.ts`:
- Around line 44-45: Filter and sanitize data.categories and data.tags to
include only string elements before assigning them (apply same change in
parseFrontmatter and getBlogPostBySlug); the current code accepts any array and
can break callers that call string methods like tag.toLowerCase() or
tags.includes(t). Replace the direct assignment of data.categories/data.tags
with a guarded expression that checks Array.isArray(...) and then filters to
keep only typeof item === 'string' (optionally trimming each string) so
downstream uses of categories/tags are safe.
---
Nitpick comments:
In `@app/`(landing)/blog/[slug]/page.tsx:
- Around line 50-61: The BlogPostPage arrow function lacks an explicit return
type; update its signature to include a Promise return type (e.g., change "const
BlogPostPage = async ({ params }: BlogPostPageProps) =>" to "const BlogPostPage
= async ({ params }: BlogPostPageProps): Promise<JSX.Element> =>") so the async
server component's return type is explicit; reference the BlogPostPage
identifier and the BlogPostPageProps type when making this change.
- Around line 11-12: The generateStaticParams function is marked async but calls
the synchronous getAllBlogPosts(), so remove the unnecessary async keyword from
generateStaticParams (and any associated await usage or unnecessary Promise
handling) so it returns the mapped slug objects synchronously; locate the
generateStaticParams declaration and change it to a plain function that returns
getAllBlogPosts().map(post => ({ slug: post.slug })).
- Line 19: Wrap the module-level function getBlogPostBySlug with React's cache()
so calls from generateMetadata and BlogPostPage share a single cached result and
avoid duplicate compileMDX/file I/O; update the exported getBlogPostBySlug
definition in the mdx/lib file to be exported as cache(async (slug: string) => {
... }) while keeping the same signature and internal logic so generateMetadata
and BlogPostPage continue calling getBlogPostBySlug unchanged.
In `@components/landing-page/blog/BlogCard.tsx`:
- Around line 87-91: Extract the inline anonymous onClick handler into a named
function called handleCardClick that accepts the event, calls
e.preventDefault(), and then invokes onCardClick?.(post.slug); replace
onClick={() => ...} with onClick={handleCardClick}. Declare handleCardClick
inside the same component (preferably wrapped with useCallback to avoid
recreating the function) and type its parameter as a
React.MouseEvent<HTMLButtonElement> if using TypeScript.
In `@lib/mdx.ts`:
- Around line 51-65: Convert the exported function declarations to const arrow
functions with explicit TypeScript return and parameter types: replace the
`function` declaration for getAllBlogPosts with `const getAllBlogPosts: () =>
MdxBlogPost[] = () => { ... }`, and do the same for getBlogPostBySlug and
getRelatedPosts (e.g., include parameter types like `slug: string` and explicit
return types). Keep existing logic, imports, and exported names identical, and
ensure the functions remain exported (export const ...) so typings and linting
conform to the project's guideline.
- Around line 99-119: getRelatedPosts currently calls getAllBlogPosts() again
causing all .mdx files to be reread; fix by avoiding duplicate I/O either by
changing getRelatedPosts to accept the precomputed posts array (e.g., add an
overload or new signature like getRelatedPosts(currentSlug, tags, categories,
posts, limit)) or by memoizing getAllBlogPosts with a module-level cache
(introduce postsCache: MdxBlogPost[] | null and return cached posts from
getAllBlogPosts when set), and update callers (e.g., getBlogPostBySlug consumer)
to pass the cached list or rely on the memoized getAllBlogPosts to prevent
double-reading.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (7)
.github/workflows/ci.ymlapp/(landing)/blog/[slug]/page.tsxcomponents/landing-page/blog/BlogCard.tsxcontent/blog/building-on-stellar.mdxcontent/blog/milestone-based-funding.mdxcontent/blog/welcome-to-boundless.mdxlib/mdx.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- content/blog/building-on-stellar.mdx
- content/blog/milestone-based-funding.mdx
| author: | ||
| name: 'Boundless Team' | ||
| image: 'https://i.pravatar.cc/150?img=1' |
There was a problem hiding this comment.
Replace placeholder avatar with a real or self-hosted image.
https://i.pravatar.cc/150?img=1 is a third-party placeholder service. Relying on it in production content creates a hard external dependency for every page render and may raise privacy/CSP concerns.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@content/blog/welcome-to-boundless.mdx` around lines 6 - 8, The frontmatter
uses an external placeholder avatar in author.image which introduces a runtime
dependency and privacy/CSP risk; replace the external URL
(https://i.pravatar.cc/150?img=1) with a real, self-hosted asset or a
repository-relative image path (or remove the image field) in the
welcome-to-boundless.mdx frontmatter so author.image points to a locally hosted
file and author.name remains unchanged.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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/pre-commit-validation.yml:
- Around line 58-62: The skip-merge guard currently uses echo/grep with the
overly-broad pattern "^Merge " which allows non-git merge-looking commits to
bypass validation; update the guard to use bash's [[ =~ ]] test (avoiding a grep
subprocess) and tighten the regex to match canonical git merge subjects (e.g.
^Merge (pull request #[0-9]+|branch .+ into .+)$ or similar canonical forms) so
only real git-generated merge commits are skipped; apply this change where the
commit subject is checked (the variable message and the current if that uses
echo "$message" | grep -qE "^Merge ") and ensure the new condition combines the
empty-message check and the tightened [[ "$message" =~ <new_regex> ]] test to
continue when appropriate.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
.github/workflows/pre-commit-validation.yml
|
@akintewe please resolve all coderabbit comments so i can merge |
|
Your implementation is solid. Ensure that codeblock, mermaid chart and badges can be rendered, alongside the table rendering |
- Normalize tag/category query values (trim + lowercase) in search route to fix case-sensitive exact-match misses - Hide 'end of posts' message when search/category filters are active - Harden frontmatter normalization: coerce all fields to string, map array elements via String(), and fix NaN-safe date sort comparator - Tighten merge-commit skip regex to canonical git forms only (branch|pull request|remote-tracking branch) in both CI workflows - Add MdxComponents.tsx with pre/code, table/thead/th/td/tr overrides, Badge (from ui/), and Mermaid fallback renderer - Wire mdxComponents map into compileMDX so code blocks, tables, badges and mermaid diagrams render correctly inside MDX posts
|
noted, working on it |
There was a problem hiding this comment.
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)
components/landing-page/blog/BlogGrid.tsx (1)
98-130:⚠️ Potential issue | 🟡 MinorRemove dead API endpoint code in
handleLoadMore.The call to
/api/blog/postsreferences a removed API route since the blog now uses file-based MDX. The function should use client-side pagination by directly incrementingvisiblePosts, which is the fallback behavior already present in the code. The current implementation causes unnecessary failed network requests on every "Load More" click.Simplify
handleLoadMoreto directly incrementvisiblePostswithout attempting to fetch from the removed API endpoint. Update the dependency array to[]since the simplified logic doesn't depend onisLoadingorhasMorePosts.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/landing-page/blog/BlogGrid.tsx` around lines 98 - 130, The handleLoadMore function should be simplified to remove the dead `/api/blog/posts` fetch: delete the network request, response handling, currentPage/hasMorePosts updates and catch/fallback branches, and instead just increment visiblePosts (via setVisiblePosts(prev => prev + 12)) and clear isLoading as needed; update the dependency array for useCallback to [] since the new logic no longer depends on isLoading, hasMorePosts or currentPage; ensure you still guard against concurrent clicks by keeping and toggling isLoading with setIsLoading at the start and end of the callback.
🧹 Nitpick comments (3)
components/landing-page/blog/BlogGrid.tsx (1)
146-152: Consider using underscore prefix for unused parameter.
void slug;works but prefixing with underscore (_slug) is more idiomatic for intentionally unused parameters.♻️ Proposed refactor
- const handleCardClick = useCallback((slug: string) => { - void slug; + const handleCardClick = useCallback((_slug: string) => { setIsNavigating(true);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/landing-page/blog/BlogGrid.tsx` around lines 146 - 152, The handleCardClick callback currently silences the unused slug parameter with "void slug;"—replace this by renaming the parameter to _slug (or _slug: string) in the handleCardClick signature to make the unused intention idiomatic, and update any internal references accordingly; leave the setIsNavigating logic and timeout unchanged.lib/mdx.ts (1)
73-108: Consider extracting shared frontmatter normalization logic.The frontmatter normalization at lines 89–105 duplicates the logic in
parseFrontmatter(lines 35–53). Extracting a shared helper would reduce maintenance burden and ensure consistency.♻️ Proposed refactor
+function normalizePostData(slug: string, data: Record<string, unknown>): MdxBlogPost { + return { + slug, + title: String(data.title ?? ''), + excerpt: String(data.excerpt ?? ''), + coverImage: String(data.coverImage ?? ''), + publishedAt: String(data.publishedAt ?? ''), + author: { + name: String(data.author?.name ?? ''), + image: String(data.author?.image ?? ''), + }, + categories: Array.isArray(data.categories) + ? data.categories.map((c: unknown) => String(c)) + : [], + tags: Array.isArray(data.tags) + ? data.tags.map((t: unknown) => String(t)) + : [], + readingTime: typeof data.readingTime === 'number' ? data.readingTime : 0, + isFeatured: data.isFeatured === true, + }; +} + function parseFrontmatter(slug: string): MdxBlogPost { const filePath = path.join(BLOG_DIR, `${slug}.mdx`); const raw = fs.readFileSync(filePath, 'utf8'); const { data } = matter(raw); - - return { - slug, - title: String(data.title ?? ''), - // ... rest of normalization - }; + return normalizePostData(slug, data); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/mdx.ts` around lines 73 - 108, getBlogPostBySlug duplicates the frontmatter normalization logic already implemented in parseFrontmatter; extract a shared helper (e.g., normalizeFrontmatter or normalizeBlogMeta) that accepts the raw frontmatter object and returns the normalized shape (title, excerpt, coverImage, publishedAt, author, categories, tags, readingTime, isFeatured) and then call that helper from both getBlogPostBySlug and parseFrontmatter so both use the same normalization routine (update function names: getBlogPostBySlug, parseFrontmatter, normalizeFrontmatter to locate usage).components/landing-page/blog/MdxComponents.tsx (1)
93-109: Mermaid fallback does not render actual diagrams.The current implementation displays raw diagram source text rather than rendered diagrams. The comment at lines 95–96 documents this limitation. If actual Mermaid rendering is needed (as mentioned in PR comments), consider adding a client component wrapper that imports
mermaid.js.Is the raw text fallback sufficient for the initial release, or should actual Mermaid diagram rendering be implemented before merge?
Do you want me to help implement a client-side Mermaid renderer component?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/landing-page/blog/MdxComponents.tsx` around lines 93 - 109, The Mermaid component currently only renders raw diagram source (function Mermaid) instead of actual diagrams; replace or augment it with a client-only component that imports mermaid, uses 'use client', and renders into a ref inside useEffect: initialize mermaid with mermaid.initialize, call mermaid.render or mermaid.parse+render to convert the children string into SVG/HTML, insert that output into a container ref, and catch/log errors; keep the existing preformatted fallback for SSR by conditionally rendering the client renderer only on the client (e.g., export a server-safe wrapper that shows the <pre> fallback and loads the client MermaidRenderer component on the client). Ensure you reference the Mermaid function and its children prop, sanitize/escape inputs if needed, and preserve styling and accessibility.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@components/landing-page/blog/StreamingBlogGrid.tsx`:
- Around line 64-70: The date comparator in StreamingBlogGrid.tsx can produce
NaN when publishedAt is missing/malformed; update the sort block that builds
filtered (the anonymous comparator inside filtered.sort) to parse each date
defensively—convert new Date(a.publishedAt).getTime() and new
Date(b.publishedAt).getTime() into numbers that fall back to 0 when NaN (e.g.,
use a || 0 or Number.isNaN checks) so the comparator always returns a valid
numeric difference and produces deterministic ordering for invalid dates.
---
Outside diff comments:
In `@components/landing-page/blog/BlogGrid.tsx`:
- Around line 98-130: The handleLoadMore function should be simplified to remove
the dead `/api/blog/posts` fetch: delete the network request, response handling,
currentPage/hasMorePosts updates and catch/fallback branches, and instead just
increment visiblePosts (via setVisiblePosts(prev => prev + 12)) and clear
isLoading as needed; update the dependency array for useCallback to [] since the
new logic no longer depends on isLoading, hasMorePosts or currentPage; ensure
you still guard against concurrent clicks by keeping and toggling isLoading with
setIsLoading at the start and end of the callback.
---
Nitpick comments:
In `@components/landing-page/blog/BlogGrid.tsx`:
- Around line 146-152: The handleCardClick callback currently silences the
unused slug parameter with "void slug;"—replace this by renaming the parameter
to _slug (or _slug: string) in the handleCardClick signature to make the unused
intention idiomatic, and update any internal references accordingly; leave the
setIsNavigating logic and timeout unchanged.
In `@components/landing-page/blog/MdxComponents.tsx`:
- Around line 93-109: The Mermaid component currently only renders raw diagram
source (function Mermaid) instead of actual diagrams; replace or augment it with
a client-only component that imports mermaid, uses 'use client', and renders
into a ref inside useEffect: initialize mermaid with mermaid.initialize, call
mermaid.render or mermaid.parse+render to convert the children string into
SVG/HTML, insert that output into a container ref, and catch/log errors; keep
the existing preformatted fallback for SSR by conditionally rendering the client
renderer only on the client (e.g., export a server-safe wrapper that shows the
<pre> fallback and loads the client MermaidRenderer component on the client).
Ensure you reference the Mermaid function and its children prop, sanitize/escape
inputs if needed, and preserve styling and accessibility.
In `@lib/mdx.ts`:
- Around line 73-108: getBlogPostBySlug duplicates the frontmatter normalization
logic already implemented in parseFrontmatter; extract a shared helper (e.g.,
normalizeFrontmatter or normalizeBlogMeta) that accepts the raw frontmatter
object and returns the normalized shape (title, excerpt, coverImage,
publishedAt, author, categories, tags, readingTime, isFeatured) and then call
that helper from both getBlogPostBySlug and parseFrontmatter so both use the
same normalization routine (update function names: getBlogPostBySlug,
parseFrontmatter, normalizeFrontmatter to locate usage).
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (7)
.github/workflows/ci.yml.github/workflows/pre-commit-validation.ymlapp/api/blog/search/route.tscomponents/landing-page/blog/BlogGrid.tsxcomponents/landing-page/blog/MdxComponents.tsxcomponents/landing-page/blog/StreamingBlogGrid.tsxlib/mdx.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- .github/workflows/ci.yml
| if (sortOrder) { | ||
| filtered = [...filtered].sort((a, b) => { | ||
| const dateA = new Date(a.coverImage).getTime(); | ||
| const dateB = new Date(b.createdAt).getTime(); | ||
| const dateA = new Date(a.publishedAt).getTime(); | ||
| const dateB = new Date(b.publishedAt).getTime(); | ||
| return sortOrder === 'Latest' ? dateB - dateA : dateA - dateB; | ||
| }); | ||
| } |
There was a problem hiding this comment.
Sort comparator may produce undefined ordering for invalid dates.
Unlike lib/mdx.ts which handles NaN dates with a fallback to 0, this comparator can return NaN if publishedAt is empty or malformed, leading to unpredictable sort order.
🛡️ Proposed fix for consistent NaN handling
if (sortOrder) {
filtered = [...filtered].sort((a, b) => {
- const dateA = new Date(a.publishedAt).getTime();
- const dateB = new Date(b.publishedAt).getTime();
- return sortOrder === 'Latest' ? dateB - dateA : dateA - dateB;
+ const ta = a.publishedAt ? new Date(a.publishedAt).getTime() : 0;
+ const tb = b.publishedAt ? new Date(b.publishedAt).getTime() : 0;
+ const dateA = isNaN(ta) ? 0 : ta;
+ const dateB = isNaN(tb) ? 0 : tb;
+ return sortOrder === 'Latest' ? dateB - dateA : dateA - dateB;
});
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@components/landing-page/blog/StreamingBlogGrid.tsx` around lines 64 - 70, The
date comparator in StreamingBlogGrid.tsx can produce NaN when publishedAt is
missing/malformed; update the sort block that builds filtered (the anonymous
comparator inside filtered.sort) to parse each date defensively—convert new
Date(a.publishedAt).getTime() and new Date(b.publishedAt).getTime() into numbers
that fall back to 0 when NaN (e.g., use a || 0 or Number.isNaN checks) so the
comparator always returns a valid numeric difference and produces deterministic
ordering for invalid dates.
GitHub's merge queue generates 'Merge <hash> into <hash>' messages which were not matched by the previous branch/PR/remote-tracking pattern.
|
i have made the fixes, please check |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
.github/workflows/ci.yml (1)
161-165: Consider extending the merge commit pattern for completeness.The current regex misses some git-generated merge message formats:
Merge commit 'abc123'— explicit commit mergeMerge branches 'a' and 'b'— octopus merge (plural)Merge tag 'v1.0'— tag mergeSuggested fix to cover additional merge formats
# Skip git-generated merge commits - if echo "$message" | grep -qE "^Merge (branch|pull request|remote-tracking branch|[0-9a-f]{7,40}) "; then + if echo "$message" | grep -qE "^Merge (branch(es)?|pull request|remote-tracking branch|commit|tag|[0-9a-f]{7,40}) "; then continue fi🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/ci.yml around lines 161 - 165, Update the grep pattern that skips git-generated merge commits (the line using echo "$message" | grep -qE "^Merge ...") to also match "Merge commit '...'", "Merge branches 'a' and 'b'" (and the singular "Merge branch 'a'"), and "Merge tag 'v1.0'". Replace the current regex with a more comprehensive one that includes alternatives for commit, tag, and branches patterns (for example: ^Merge (commit '[^']+'|tags? '[^']+'|branches? '[^']+'( and '[^']+')?|branch|pull request|remote-tracking branch|[0-9a-f]{7,40}) ) so the grep -qE call inside that if block will correctly skip these additional git-generated merge message formats.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In @.github/workflows/ci.yml:
- Around line 161-165: Update the grep pattern that skips git-generated merge
commits (the line using echo "$message" | grep -qE "^Merge ...") to also match
"Merge commit '...'", "Merge branches 'a' and 'b'" (and the singular "Merge
branch 'a'"), and "Merge tag 'v1.0'". Replace the current regex with a more
comprehensive one that includes alternatives for commit, tag, and branches
patterns (for example: ^Merge (commit '[^']+'|tags? '[^']+'|branches? '[^']+'(
and '[^']+')?|branch|pull request|remote-tracking branch|[0-9a-f]{7,40}) ) so
the grep -qE call inside that if block will correctly skip these additional
git-generated merge message formats.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
.github/workflows/ci.yml.github/workflows/pre-commit-validation.yml
🚧 Files skipped from review as they are similar to previous changes (1)
- .github/workflows/pre-commit-validation.yml

Summary
/blog-posts) to file-based MDX content incontent/blog/useMarkdownhook + 650-line inline CSS renderer withnext-mdx-remote+ Tailwind Typography (proseclasses)lib/mdx.tswithgetAllBlogPosts,getBlogPostBySlug, andgetRelatedPostsutilitieswelcome-to-boundless,building-on-stellar,milestone-based-funding)useEffectfetchlib/api/blog.ts(fully replaced bylib/mdx.ts)app/api/blog/search/route.tsandapp/sitemap.tsto use the MDX data sourcenext-mdx-remote,gray-matter,@tailwindcss/typographyChanges
New files
lib/mdx.ts— MDX utility functions andMdxBlogPosttypecontent/blog/welcome-to-boundless.mdxcontent/blog/building-on-stellar.mdxcontent/blog/milestone-based-funding.mdxModified files
app/(landing)/blog/page.tsx— async server component, callsgetAllBlogPosts()app/(landing)/blog/[slug]/page.tsx—generateStaticParams+generateMetadatafrom MDX; renders viagetBlogPostBySlugcomponents/landing-page/blog/BlogPostDetails.tsx— renders compiled MDX viaprosediv; removesuseMarkdowncomponents/landing-page/blog/StreamingBlogGrid.tsx— usesMdxBlogPost[]; client-side visible count paginationcomponents/landing-page/blog/BlogCard.tsx— usesMdxBlogPostcomponents/landing-page/blog/BlogGrid.tsx— usesMdxBlogPostcomponents/landing-page/blog/BlogSection.tsx— synchronousgetAllBlogPosts()callcomponents/landing-page/blog/BlogSectionClient.tsx— usesMdxBlogPostapp/api/blog/search/route.ts— usesgetAllBlogPosts()fromlib/mdxapp/globals.css— adds@plugin "@tailwindcss/typography"+ dark prose CSS variable overridesapp/sitemap.ts—fetchBlogPostsSitemapnow reads MDX files synchronouslyDeleted files
lib/api/blog.ts— replaced bylib/mdx.tsTest plan
npm run buildcompletes with no errors and statically generates all/blog/[slug]routes/blogpage renders the 3 sample posts without a loading flashview-source:/blog/welcome-to-boundlessshows content in the HTML response (SSG confirmed)/sitemap.xmlincludes all 3 blog post URLsScreen.Recording.2026-02-25.at.1.56.11.PM.1.mp4
Closes #407
Summary by CodeRabbit
New Features
Improvements
Chores