feat(landing): add canonical blog path#187
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThis PR adds a new ChangesLanding Blog Feature
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Browser
participant BlogIndex as blog/index.astro
participant BlogSlug as [slug].astro
participant BlogLib as blog.ts
participant Content as astro:content
participant LandingLayout
Browser->>BlogIndex: request /blog/
BlogIndex->>BlogLib: getSortedBlogPosts()
BlogLib->>Content: load blog collection
Content-->>BlogLib: entries
BlogLib-->>BlogIndex: sorted posts
BlogIndex->>LandingLayout: render index with canonicalUrl
Browser->>BlogSlug: request /blog/<slug>/
BlogSlug->>BlogLib: getSortedBlogPosts()
BlogSlug->>Content: render(entry)
BlogSlug->>LandingLayout: render post with canonicalUrl
LandingLayout-->>Browser: HTML
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ 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 |
|
| Filename | Overview |
|---|---|
| apps/landing/src/scripts/observability.ts | Classifies blog pageviews and click intents with source-page route and page categories. |
| apps/landing/src/pages/blog/[slug].astro | Adds static blog post rendering with slug-derived canonical metadata. |
| apps/landing/src/pages/blog/index.astro | Adds the blog index page with sorted public posts. |
| apps/landing/src/content.config.ts | Defines the typed static blog content collection. |
| apps/landing/src/lib/blog.ts | Adds shared blog URL, sorting, and collection helpers. |
| packages/web-observability/src/events.ts | Adds blog route and event categories to the shared event contract. |
| packages/web-observability/src/privacy.ts | Allows only categorical blog values through the web observability privacy filter. |
Reviews (7): Last reviewed commit: "copy(landing): reframe MCP client moat" | Re-trigger Greptile
Preview DeployedLanding: https://pr-187.preview.caplets.dev Built from commit 54e52c3 |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (5)
apps/landing/src/content.config.ts (1)
12-12: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win
canonicalPathis a manually duplicated source of truth.The schema validates
canonicalPathas a free-form string separate from the collection's ownid/slug. Per the downstream[slug].astroroute, the actual canonical<link>is derived independently viablogPostUrl(entry.slug), whileBlogArticlerendersentry.data.canonicalPathas the displayed "Canonical URL" text. Two independent sources for what should be the same value means a typo in frontmatter (or a future slug rename) can silently desync the displayed URL from the real canonical URL, which is an SEO-relevant correctness issue.Consider deriving the displayed canonical path from the slug (e.g., pass
blogPostUrl(entry.slug)intoBlogArticleinstead of a separate frontmatter field), or add a schema-level/test-level assertion thatcanonicalPath === blogPostUrl(id)for every entry.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/landing/src/content.config.ts` at line 12, The canonicalPath field is duplicating the slug-derived canonical URL and can drift out of sync. Update the blog article flow so BlogArticle uses blogPostUrl(entry.slug) (or the equivalent slug-derived value) instead of relying on entry.data.canonicalPath, and adjust content.config.ts to remove the free-form source of truth or replace it with a schema/test assertion that ties canonicalPath to the entry slug/id.apps/landing/src/layouts/LandingLayout.astro (2)
40-42: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winMissing
twitter:imagetag.
twitter:cardissummary_large_imagebut notwitter:imageis set. X falls back toog:imagewhen absent, so this isn't broken, but explicitly setting it is the documented best practice for card redundancy — worth adding alongside the othertwitter:*tags for consistency with this PR's social-metadata goal.✏️ Suggested addition
<meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:title" content={title} /> <meta name="twitter:description" content={description} /> + <meta name="twitter:image" content={ogImageUrl} />🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/landing/src/layouts/LandingLayout.astro` around lines 40 - 42, The Twitter metadata block in LandingLayout should include an explicit twitter:image because twitter:card is set to summary_large_image. Update the existing twitter:* meta group in LandingLayout to add the image tag alongside twitter:title and twitter:description, using the same image source already used for the page’s social metadata.
14-23: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winCentralize the landing site URL
https://caplets.devis duplicated inapps/landing/src/layouts/LandingLayout.astroand the blog pages. Move it to one shared source of truth, or setsitein the landing Astro config and derive canonical/OG URLs from that instead.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/landing/src/layouts/LandingLayout.astro` around lines 14 - 23, Move the hardcoded landing origin out of LandingLayout.astro and the blog pages into one shared source of truth, ideally the landing Astro config via the site setting. Update LandingLayout and any blog URL builders to derive canonicalHref and ogImageUrl from that shared site value instead of duplicating "https://caplets.dev", using the existing canonicalUrl and ogImage props where applicable.apps/landing/src/pages/blog/index.astro (1)
18-18: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win
featuredTitleduplicates the post's frontmatter title as a literal.This hardcoded string will silently drift from the actual post title if the frontmatter (
src/content/blog/why-giant-mcp-tool-walls-dont-scale.md) is ever updated, since nothing keeps them in sync. Deriving it from the already-fetchedpostsarray avoids the duplication.♻️ Suggested fix
-const featuredTitle = "Why Giant MCP Tool Walls Don’t Scale"; +const featuredTitle = posts[0]?.data.title ?? "our launch essay";🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/landing/src/pages/blog/index.astro` at line 18, The featured title in the blog index is hardcoded and duplicates the frontmatter title, so it can drift from the source post metadata. Update the logic in the blog page component to derive featuredTitle from the already loaded posts array instead of using a literal, using the relevant post entry by slug or position so it stays in sync with the content source.apps/landing/src/pages/blog/[slug].astro (1)
11-23: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated collection-fetch/sort logic with
pages/blog/index.astro.The
(await getCollection("blog")).map((entry) => ({ ...entry, slug: entry.id }))+sortBlogPostsNewestFirst(...)sequence is duplicated verbatim inpages/blog/index.astro(Lines 12-17). Consider extracting a sharedgetSortedBlogPosts()helper inlib/blog.tsso both routes share one implementation.♻️ Suggested extraction (lib/blog.ts)
export async function getSortedBlogPosts() { const { getCollection } = await import("astro:content"); return sortBlogPostsNewestFirst( (await getCollection("blog")).map((entry) => ({ ...entry, slug: entry.id })), ); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/landing/src/pages/blog/`[slug].astro around lines 11 - 23, The blog post collection fetch and newest-first sort are duplicated between the slug page and the blog index, so extract that shared logic into a helper such as getSortedBlogPosts in lib/blog.ts. Update getStaticPaths in pages/blog/[slug].astro and the index route to call the shared helper instead of repeating the getCollection("blog") plus sortBlogPostsNewestFirst mapping sequence.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/landing/src/components/landing/Header.astro`:
- Around line 25-26: The new and modified nav links in Header.astro are missing
the focus-visible ring color class, causing inconsistent keyboard focus styling.
Update the affected anchor elements for the Blog and Remote links to match the
other site-header__section-link entries by restoring the
focus-visible:ring-outline/50 class alongside focus-visible:ring-3 and
focus-visible:outline-none, so the focus ring remains consistent across all nav
items.
In `@apps/landing/src/pages/blog/`[slug].astro:
- Around line 25-38: The blog post page is using two separate canonical path
sources: `canonicalUrl` is derived from `entry.slug`, while `BlogArticle`
receives `entry.data.canonicalPath` from frontmatter. Update the `[slug].astro`
page so `BlogArticle` uses the slug-derived path (or otherwise assert the two
values match at build time) to keep the canonical/OG URL consistent. Use the
existing `canonicalUrl`, `entry.slug`, and `BlogArticle` props as the
identifiers to locate the fix.
---
Nitpick comments:
In `@apps/landing/src/content.config.ts`:
- Line 12: The canonicalPath field is duplicating the slug-derived canonical URL
and can drift out of sync. Update the blog article flow so BlogArticle uses
blogPostUrl(entry.slug) (or the equivalent slug-derived value) instead of
relying on entry.data.canonicalPath, and adjust content.config.ts to remove the
free-form source of truth or replace it with a schema/test assertion that ties
canonicalPath to the entry slug/id.
In `@apps/landing/src/layouts/LandingLayout.astro`:
- Around line 40-42: The Twitter metadata block in LandingLayout should include
an explicit twitter:image because twitter:card is set to summary_large_image.
Update the existing twitter:* meta group in LandingLayout to add the image tag
alongside twitter:title and twitter:description, using the same image source
already used for the page’s social metadata.
- Around line 14-23: Move the hardcoded landing origin out of
LandingLayout.astro and the blog pages into one shared source of truth, ideally
the landing Astro config via the site setting. Update LandingLayout and any blog
URL builders to derive canonicalHref and ogImageUrl from that shared site value
instead of duplicating "https://caplets.dev", using the existing canonicalUrl
and ogImage props where applicable.
In `@apps/landing/src/pages/blog/`[slug].astro:
- Around line 11-23: The blog post collection fetch and newest-first sort are
duplicated between the slug page and the blog index, so extract that shared
logic into a helper such as getSortedBlogPosts in lib/blog.ts. Update
getStaticPaths in pages/blog/[slug].astro and the index route to call the shared
helper instead of repeating the getCollection("blog") plus
sortBlogPostsNewestFirst mapping sequence.
In `@apps/landing/src/pages/blog/index.astro`:
- Line 18: The featured title in the blog index is hardcoded and duplicates the
frontmatter title, so it can drift from the source post metadata. Update the
logic in the blog page component to derive featuredTitle from the already loaded
posts array instead of using a literal, using the relevant post entry by slug or
position so it stays in sync with the content source.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 56eb253d-9369-4c51-b50f-eaed2638c0ae
📒 Files selected for processing (21)
apps/landing/src/components/landing/BlogArticle.astroapps/landing/src/components/landing/BlogCta.astroapps/landing/src/components/landing/Footer.astroapps/landing/src/components/landing/Header.astroapps/landing/src/content.config.tsapps/landing/src/content/blog/why-giant-mcp-tool-walls-dont-scale.mdapps/landing/src/layouts/LandingLayout.astroapps/landing/src/lib/blog.tsapps/landing/src/pages/blog/[slug].astroapps/landing/src/pages/blog/index.astroapps/landing/src/scripts/observability.tsapps/landing/src/styles/global.cssapps/landing/test/blog-content.test.tsapps/landing/test/blog-metadata.test.tsapps/landing/test/blog-navigation.test.tsapps/landing/test/blog-routes.test.tsapps/landing/test/observability.test.tsdocs/plans/2026-07-01-003-feat-landing-blog-path-plan.mdpackages/web-observability/src/events.tspackages/web-observability/src/privacy.tspackages/web-observability/test/web-observability.test.ts
Summary
/blogsurface to the Astro landing app./blog/why-giant-mcp-tool-walls-dont-scale/.Verification
pnpm --filter @caplets/landing testpnpm --filter @caplets/landing typecheckpnpm --filter @caplets/landing buildpnpm --filter @caplets/web-observability testpnpm --filter @caplets/web-observability typecheckpnpm format:checkpnpm lintpnpm verifycompleted successfully before pushingfeat/landing-blog-path.Notes
Plan:
docs/plans/2026-07-01-003-feat-landing-blog-path-plan.mdSummary by CodeRabbit
/blogsurface.