-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add feature related post #7
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
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Reviewer's GuideImplements a related posts feature for blog articles by computing related posts based on tags and category, exposing the logic via a new utility, and rendering them in a new RelatedPosts component within the existing BlogPostLayout. Sequence diagram for computing and rendering related blog postssequenceDiagram
actor User
participant BlogPostPage as BlogPostLayout
participant ContentAPI as AstroContent
participant Utils as post_utils
participant Related as RelatedPosts
participant Card as BlogPostCard
User->>BlogPostPage: Request blog post
BlogPostPage->>ContentAPI: getCollection(blog)
ContentAPI-->>BlogPostPage: allPosts
BlogPostPage->>Utils: getRelatedPosts(allPosts, postSlug, tags, category, 3)
Utils-->>BlogPostPage: relatedPosts
BlogPostPage->>Related: Render RelatedPosts with relatedPosts
alt has relatedPosts
loop for each relatedPost
Related->>Card: Render BlogPostCard(post)
Card-->>Related: Blog post card HTML
end
else no relatedPosts
Related-->>BlogPostPage: No related posts section rendered
end
BlogPostPage-->>User: Full blog post page HTML including related posts
Class diagram for related posts utility and componentsclassDiagram
class BlogPostLayout {
+string title
+string description
+Date pubDate
+string slug
+string[] tags
+string category
+string postSlug
+CollectionEntry_blog_[] allPosts
+CollectionEntry_blog_[] relatedPosts
+render()
}
class RelatedPosts {
+CollectionEntry_blog_[] relatedPosts
+render()
}
class BlogPostCard {
+CollectionEntry_blog_ post
+render()
}
class post_utils {
+getRelatedPosts(allPosts CollectionEntry_blog_[], currentSlug string, currentTags string[], currentCategory string, limit number) CollectionEntry_blog_[]
}
class CollectionEntry_blog_ {
+string slug
+data Data_blog_
}
class Data_blog_ {
+string[] tags
+string category
+Date pubDate
}
BlogPostLayout --> post_utils : uses
BlogPostLayout --> RelatedPosts : renders
RelatedPosts --> BlogPostCard : renders
BlogPostLayout --> CollectionEntry_blog_ : gets_from_getCollection
CollectionEntry_blog_ --> Data_blog_ : has_data
post_utils --> CollectionEntry_blog_ : processes
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
📝 WalkthroughWalkthroughAdds a cached blog posts accessor, a scoring-based related-posts utility, a new RelatedPosts Astro component, and integrates related-post rendering into BlogPostLayout to display up to three related posts beneath a post. Changes
Sequence DiagramsequenceDiagram
participant Layout as BlogPostLayout
participant Cache as getAllBlogPosts
participant Util as getRelatedPosts
participant Component as RelatedPosts
participant UI as Browser
Layout->>Cache: fetch allPosts[]
Cache-->>Layout: return cached allPosts[]
Note over Layout: determine currentSlug, tags, category
Layout->>Util: getRelatedPosts(allPosts, currentSlug, tags, category, 3)
Util->>Util: filter & score posts\n(sort by score, then pubDate)
Util-->>Layout: relatedPosts[]
Layout->>Component: render with relatedPosts
Component->>Component: build responsive grid\n(render BlogPostCard per item)
Component-->>UI: display "You might also like" section
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
Pre-merge checks and finishing touches❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
✨ Finishing touches
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey - I've left some high level feedback:
- The
getRelatedPostshelper assumescurrentTagsandcurrentCategoryare always defined and that all posts havedata.pubDate; consider adding null/undefined guards or default values to avoid runtime errors when frontmatter is missing or incomplete. - Fetching
allPostsviagetCollection('blog')insideBlogPostLayoutmeans this runs on every post render; consider moving this to a higher level or caching the collection so related posts are computed without repeatedly querying all content. - In
RelatedPosts.astro, therelatedPosts.maploop rendersBlogPostCardwithout a stable key; consider passing a unique key (e.g.,post.idorpost.slug) to help Astro/underlying framework optimize DOM updates.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The `getRelatedPosts` helper assumes `currentTags` and `currentCategory` are always defined and that all posts have `data.pubDate`; consider adding null/undefined guards or default values to avoid runtime errors when frontmatter is missing or incomplete.
- Fetching `allPosts` via `getCollection('blog')` inside `BlogPostLayout` means this runs on every post render; consider moving this to a higher level or caching the collection so related posts are computed without repeatedly querying all content.
- In `RelatedPosts.astro`, the `relatedPosts.map` loop renders `BlogPostCard` without a stable key; consider passing a unique key (e.g., `post.id` or `post.slug`) to help Astro/underlying framework optimize DOM updates.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
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.
Actionable comments posted: 0
🧹 Nitpick comments (2)
src/utils/post-utils.ts (1)
50-64: Consider edge case: handling when allPosts contains only the current post.The logic correctly returns related posts or falls back to recent posts. However, there's a subtle edge case: if
allPostscontains only the current post, both branches will return an empty array (which is correct), but it might be worth noting this behavior in a comment for future maintainers.📝 Optional: Add clarifying comment
// Check if we have any posts with score > 0 const postsWithScore = scoredPosts.filter(item => item.score > 0); if (postsWithScore.length > 0) { // Return top 'limit' related posts return postsWithScore.slice(0, limit).map(item => item.post); } - // Fallback: return most recent posts if no related posts found + // Fallback: return most recent posts if no related posts found + // Returns empty array if only the current post exists const recentPosts = [...otherPosts].sort( (a, b) => new Date(b.data.pubDate).getTime() - new Date(a.data.pubDate).getTime(), ); return recentPosts.slice(0, limit);src/components/blog/RelatedPosts.astro (1)
13-24: Consider adding a unique key for better performance.The conditional rendering and mapping logic is correct. However, Astro (like React) benefits from explicit keys in mapped lists to optimize re-rendering, especially for dynamic content.
🔎 Add key attribute to mapped items
<div class="related-posts-grid"> - {relatedPosts.map(post => ( - <BlogPostCard post={post} /> + {relatedPosts.map(post => ( + <BlogPostCard key={post.slug} post={post} /> ))} </div>
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/components/blog/RelatedPosts.astrosrc/layouts/BlogPostLayout.astrosrc/utils/post-utils.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Sourcery review
- GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (9)
src/utils/post-utils.ts (4)
8-14: LGTM! Well-documented function signature.The function signature is clear with appropriate TypeScript types and a sensible default for the limit parameter. The JSDoc comment effectively explains the scoring system and fallback behavior.
15-16: LGTM! Correct filtering of the current post.Properly excludes the current post from the results to avoid self-referencing.
38-48: LGTM! Solid sorting logic with proper tie-breaking.The sort implementation correctly prioritizes score first, then uses publication date as a tie-breaker, ensuring newer posts are preferred when scores are equal.
18-36: The scoring logic is correctly implemented. However, the case-sensitivity concern is not supported by the actual data in this repository—all tags and categories are consistently title-cased (e.g.,'JavaScript','DevOps','Git'). No case variations like"javascript"or"devops"exist in the blog posts, making the current direct string comparison appropriate. Making comparisons case-insensitive would add unnecessary overhead without addressing a real issue.Likely an incorrect or invalid review comment.
src/components/blog/RelatedPosts.astro (2)
1-11: LGTM! Properly typed Astro component.The component is correctly set up with TypeScript types using Astro's
CollectionEntrytype, and the props destructuring follows Astro v5 conventions.
26-68: LGTM! Well-structured responsive design.The CSS provides a clean, responsive layout with appropriate breakpoints and uses CSS custom properties for theming. The grid gracefully adapts from 3 columns → 2 columns → 1 column as viewport narrows.
src/layouts/BlogPostLayout.astro (3)
5-10: LGTM! Appropriate imports for the related posts feature.The imports are correctly structured and use Astro v5's stable content collections API (not the deprecated legacy system).
84-84: LGTM! Clean integration of the RelatedPosts component.The component is placed logically after the post footer within the article, providing a natural flow for readers to discover related content.
30-35: No changes needed. This is a static site (confirmed viaastro.config.mjswith Vercel adapter and default static output mode). For static generation,getCollection('blog')executing at build time is standard Astro behavior and incurs no runtime penalty. The review comment's concerns about SSR and server-side performance do not apply to this codebase.
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.
Actionable comments posted: 0
🧹 Nitpick comments (4)
src/lib/blog.ts (1)
1-22: Consider the implications of module-level caching in development mode.The module-level cache works well for production builds where Astro pre-renders pages once. However, during development with Hot Module Replacement (HMR), the cache persists across file changes and won't reflect newly added or modified blog posts until the dev server is restarted.
Recommended approaches
Option 1: Add cache invalidation in development
+import.meta.env?.DEV && (cachedPosts = null); + export async function getAllBlogPosts(): Promise<CollectionEntry<'blog'>[]> { - if (cachedPosts === null) { + if (cachedPosts === null || import.meta.env?.DEV) { cachedPosts = await getCollection('blog'); } return cachedPosts; }Option 2: Document the limitation
/** * Get all blog posts with caching. * Results are cached at the module level to avoid repeated collection fetches. + * Note: In development mode, restart the dev server to refresh cached posts. */src/layouts/BlogPostLayout.astro (2)
29-30: Clarify the "database key" comment.The term "database key" may be misleading since the slug is used for the ViewCounter component and related posts computation, not necessarily direct database access. Consider updating the comment to better reflect its actual usage.
Suggested wording
-// Use the provided slug or generate from title as a database key +// Use the provided slug or generate from title for view tracking and related posts const postSlug = slug || slugify(title);
32-34: Consider adding error handling for blog posts fetching.If
getAllBlogPosts()throws an error, the entire page build will fail. Consider adding error handling to gracefully degrade the related posts feature.Proposed error handling
-// Fetch all blog posts (cached) and compute related posts -const allPosts = await getAllBlogPosts(); -const relatedPosts = getRelatedPosts(allPosts, postSlug, tags, category, 3); +// Fetch all blog posts (cached) and compute related posts +let relatedPosts: CollectionEntry<'blog'>[] = []; +try { + const allPosts = await getAllBlogPosts(); + relatedPosts = getRelatedPosts(allPosts, postSlug, tags, category, 3); +} catch (error) { + console.error('Failed to fetch related posts:', error); +}src/components/blog/RelatedPosts.astro (1)
43-47: Consider grid behavior with fewer than 3 related posts.The 3-column grid layout works well when you have exactly 3 related posts, but with only 1 or 2 posts, the items may look isolated or asymmetric on large screens. Consider whether this is the desired visual effect.
Optional: Center items when fewer than 3 posts
.related-posts-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.5rem; + justify-items: center; } +.related-posts-grid:has(.grid-item:last-child:nth-child(-n+2)) { + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + max-width: 600px; + margin: 0 auto; +}
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
src/components/blog/RelatedPosts.astrosrc/layouts/BlogPostLayout.astrosrc/lib/blog.tssrc/utils/post-utils.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- src/utils/post-utils.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Sourcery review
- GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (3)
src/layouts/BlogPostLayout.astro (1)
5-9: Related posts integration looks good.The imports are clean and the integration of the RelatedPosts component is straightforward and well-placed after the post footer.
Also applies to: 83-83
src/components/blog/RelatedPosts.astro (2)
1-11: Well-structured component with clean conditional rendering.The component structure is solid with proper typing, conditional rendering to avoid empty sections, and responsive CSS. The use of BlogPostCard ensures consistency with the rest of the blog UI.
Also applies to: 13-26, 28-74
19-19: No changes required. The code correctly usespost.slug. In Astro v5'sastro:contentAPI,slugis a built-in property derived from the file structure (not defined in the schema), and it is the correct property to use forCollectionEntry<'blog'>. Theidproperty does not exist on this type. The codebase consistently usespost.slugthroughout (in routing, links, and utilities), confirming this is the correct approach.Likely an incorrect or invalid review comment.
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.
Actionable comments posted: 1
🧹 Nitpick comments (2)
src/utils/post-utils.ts (2)
34-40: Consider using a Set for tag matching performance.The current implementation uses
includes()inside a nested loop, resulting in O(n × m × k) complexity. ConvertingcurrentTagsto a Set would improve tag lookups to O(1), reducing complexity to O(n × m).🔎 Proposed optimization
// Calculate scores for each post + const currentTagsSet = new Set(currentTags); const scoredPosts = otherPosts.map((post) => { let score = 0; // +2 points for each matching tag const postTags = post.data.tags ?? []; for (const tag of postTags) { - if (currentTags.includes(tag)) { + if (currentTagsSet.has(tag)) { score += 2; } }
68-68: Optional: Remove redundant array spread.The spread operator
[...otherPosts]creates an unnecessary copy sinceotherPostsis already a new array from thefilter()operation and isn't reused afterward.🔎 Proposed simplification
// Fallback: return most recent posts if no related posts found - const recentPosts = [...otherPosts].sort( + const recentPosts = otherPosts.sort( (a, b) => safeGetTime(b.data.pubDate) - safeGetTime(a.data.pubDate), );
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/utils/post-utils.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Sourcery review
- GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (2)
src/utils/post-utils.ts (2)
6-11: LGTM! Safe date parsing implementation.The
safeGetTimehelper correctly handles undefined/null dates and invalid date parsing, returning 0 as a safe fallback for sorting operations.
18-28: LGTM! Clean function signature and filtering logic.The default parameters are sensible, and the filtering correctly excludes the current post and posts without valid publication dates.
| const otherPosts = allPosts.filter( | ||
| post => post.slug !== currentSlug && post.data.pubDate, | ||
| ); |
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.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for getAllBlogPosts implementation
echo "=== Searching for getAllBlogPosts ==="
rg -n --type=ts "getAllBlogPosts" --max-count=10
echo ""
echo "=== Checking post-utils.ts context ==="
# Read the file to see the actual implementation context
cat -n src/utils/post-utils.ts | head -50
echo ""
echo "=== Searching for draft filtering logic ==="
rg -n --type=ts -C3 "draft|published" src/Repository: pharmacist-sabot/rxdevnotes
Length of output: 2075
🏁 Script executed:
#!/bin/bash
# Read the getAllBlogPosts implementation
echo "=== getAllBlogPosts implementation ==="
cat -n src/lib/blog.ts
echo ""
echo "=== Search for content schema definition ==="
fd -e ts -e tsx "schema\|config" src/ | head -10
echo ""
echo "=== Look for blog collection definition ==="
rg -n "type.*blog\|interface.*blog" --type=ts -i src/Repository: pharmacist-sabot/rxdevnotes
Length of output: 1069
🏁 Script executed:
#!/bin/bash
# Find Astro content config file
echo "=== Looking for Astro content configuration ==="
fd -name "config.ts" src/content/
echo ""
echo "=== Check directory structure of src/content ==="
find src/content -type f | head -20
echo ""
echo "=== Search for any draft-related filtering in getCollection calls ==="
rg -n "getCollection" --type=ts -A5 -B5 src/Repository: pharmacist-sabot/rxdevnotes
Length of output: 2219
🏁 Script executed:
#!/bin/bash
# Read the Astro content config
echo "=== Astro content config ==="
cat -n src/content/config.ts
echo ""
echo "=== Check for draft field in blog frontmatter ==="
# Sample one blog post to see the frontmatter
head -50 src/content/blog/automate-release-with-semantic-release/index.mdxRepository: pharmacist-sabot/rxdevnotes
Length of output: 3191
Consider adding draft post support if needed. The codebase currently lacks a draft field in the blog collection schema and has no draft filtering logic. If draft posts are needed, add a draft: z.boolean().optional() field to the schema in src/content/config.ts and implement filtering in getAllBlogPosts() to exclude them (e.g., await getCollection('blog').then(posts => posts.filter(p => !p.data.draft))). Currently, the getRelatedPosts() function only filters by pubDate and slug, which is insufficient for draft post handling.
🤖 Prompt for AI Agents
In src/utils/post-utils.ts around lines 26 to 28, getRelatedPosts currently
filters only by slug and pubDate and will include draft posts if drafts exist;
add draft handling by adding a draft: z.boolean().optional() to the blog
collection schema in src/content/config.ts and update the blog post retrieval to
exclude drafts (e.g., when calling getCollection('blog') filter out posts with
data.draft === true) so that otherPosts excludes drafts as well as unpublished
posts.
## [1.3.0](v1.2.2...v1.3.0) (2025-12-31) ### 🚀 New Features * add feature related post ([#7](#7)) ([ab0c07c](ab0c07c))
|
🎉 This PR is included in version 1.3.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
Summary by Sourcery
Add related blog post recommendations to the blog post layout using tag and category similarity.
New Features:
Summary by CodeRabbit
New Features
Chores / Performance
✏️ Tip: You can customize this high-level summary in your review settings.