feat(marketing): add related posts section to blog post pages#1147
Conversation
Add a "Related Posts" section above the footer on blog post pages. Posts can specify related posts via `relatedSlugs` frontmatter field, falling back to the 3 most recent other posts when not specified.
📝 WalkthroughWalkthroughThis pull request adds a related posts feature to the blog system. Three blog post files receive Changes
Sequence Diagram(s)sequenceDiagram
participant Page as Blog Page<br/>[slug]
participant BlogUtil as Blog Utilities<br/>(blog.ts)
participant Layout as BlogPostLayout<br/>Component
participant Card as BlogCard<br/>Component
participant Data as BlogPost<br/>Data
Page->>BlogUtil: getRelatedPosts({ slug, relatedSlugs })
BlogUtil->>Data: filter posts matching relatedSlugs<br/>or get first 3 posts
Data-->>BlogUtil: return related BlogPost[]
BlogUtil-->>Page: relatedPosts
Page->>Layout: pass post, relatedPosts,<br/>children
Layout->>Layout: check relatedPosts<br/>is non-empty
alt relatedPosts exists
Layout->>Card: map & render BlogCard<br/>for each related post
Card-->>Layout: rendered card
end
Layout-->>Page: rendered layout<br/>with related section
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ 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: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/marketing/src/lib/blog.ts (2)
32-42:⚠️ Potential issue | 🟠 MajorNormalize
relatedSlugsto a string array before storing.If frontmatter provides a single string (or non-array),
getRelatedPostswill throw when calling.map().🛡️ Proposed normalization
return { slug, url: `/blog/${slug}`, title: data.title ?? "Untitled", description: data.description, author: data.author ?? "Unknown", date: dateValue, category: data.category ?? "News", image: data.image, - relatedSlugs: data.relatedSlugs, + relatedSlugs: Array.isArray(data.relatedSlugs) + ? data.relatedSlugs.filter( + (slug): slug is string => + typeof slug === "string" && slug.length > 0 + ) + : undefined, content, };
44-45:⚠️ Potential issue | 🟠 MajorDon’t swallow frontmatter parse errors; log with context.
Silent failures make missing posts hard to diagnose.
🧾 Suggested logging
- } catch { - return null; - } + } catch (error) { + console.error("[blog/parseFrontmatter] Failed to parse blog post", { + filePath, + error, + }); + return null; + }
🧹 Nitpick comments (3)
apps/marketing/src/lib/blog-utils.ts (1)
14-24: Document whyrelatedSlugsis optional.Guideline calls for documenting optional interface fields.
♻️ Proposed doc addition
export interface BlogPost { slug: string; url: string; title: string; description?: string; author: string; date: string; category: BlogCategory; image?: string; + /** + * Optional frontmatter list of related post slugs. + * When omitted, related posts fall back to recent posts. + */ relatedSlugs?: string[]; content: string; }apps/marketing/src/lib/blog.ts (1)
97-100: Filter out the current slug in explicit related lists.Prevents self-links when an author accidentally includes the current post.
♻️ Proposed tweak
if (relatedSlugs && relatedSlugs.length > 0) { return relatedSlugs + .filter((s) => s !== slug) .map((s) => getBlogPost(s)) .filter((post): post is BlogPost => post !== undefined); }apps/marketing/src/app/blog/[slug]/components/BlogPostLayout/BlogPostLayout.tsx (1)
97-111: Extract the 200px min width to a named constant.
Avoids a magic number and makes future layout tweaks safer.As per coding guidelines: "Avoid magic numbers by extracting them to named constants at module top."♻️ Proposed refactor
import { GridCross } from "@/app/blog/components/GridCross"; import { type BlogPost, formatBlogDate, type TocItem } from "@/lib/blog-utils"; +const RELATED_POSTS_MIN_WIDTH_PX = 200; + interface BlogPostLayoutProps { post: BlogPost; toc: TocItem[]; relatedPosts: BlogPost[]; @@ <h2 className="text-xl font-medium text-foreground mb-6"> Related Posts </h2> - <div className="grid gap-4 grid-cols-[repeat(auto-fit,minmax(200px,1fr))]"> + <div + className="grid gap-4" + style={{ + gridTemplateColumns: `repeat(auto-fit, minmax(${RELATED_POSTS_MIN_WIDTH_PX}px, 1fr))`, + }} + > {relatedPosts.map((relatedPost) => ( <BlogCard key={relatedPost.slug} post={relatedPost} /> ))} </div>
🧹 Preview Cleanup CompleteThe following preview resources have been cleaned up:
Thank you for your contribution! 🎉 |
Summary
BlogCardcomponentsrelatedSlugsfrontmatter array; falls back to the 3 most recent other posts when not specifiedTest plan
bun dev --filter=@superset/marketingand visit each blog postrelatedSlugsfrom one post's frontmatter and confirm fallback shows other postsSummary by CodeRabbit
Release Notes