Skip to content

Add prev/next nav to blog posts, reduce index gap - #859

Merged
lawrencecchen merged 3 commits into
mainfrom
fix-blog-nav
Mar 4, 2026
Merged

lawrencecchen merged 3 commits into
mainfrom
fix-blog-nav

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Mar 4, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Add prev/next pager at the bottom of each blog post (matches docs pattern)
  • Extract shared blogPosts array into web/app/components/blog-posts.ts
  • Reduce blog index vertical gap from space-y-8 to space-y-4

Testing

  • Check Vercel preview: each blog post should show prev/next links at the bottom
  • Blog index at /blog should have tighter spacing between entries

Summary by cubic

Adds prev/next navigation and a Download/GitHub CTA to all blog posts, with PostHog click events including the post slug. Tightens blog index spacing for a cleaner reading flow.

  • New Features

    • Prev/next pager at the bottom of posts.
    • Download/GitHub CTA on posts (hidden on /blog); removed duplicates on show-hn-launch.
    • PostHog click tracking now includes the blog post slug.
  • Refactors

    • Centralized blogPosts in web/app/components/blog-posts.ts.
    • Reduced blog index spacing from space-y-8 to space-y-4.

Written for commit d7f4191. Summary will update on new commits.

Summary by CodeRabbit

  • New Features
    • Added previous/next navigation between blog posts and a contextual blog call-to-action to improve browsing and engagement.
  • Data
    • Centralized blog post data into a reusable list for consistent rendering across the blog.
  • Style
    • Adjusted vertical spacing on blog pages for improved readability.
  • Chores
    • Removed redundant download and GitHub buttons from one blog post page.

@vercel

vercel Bot commented Mar 4, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Mar 4, 2026 9:46am

@coderabbitai

coderabbitai Bot commented Mar 4, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds centralized blog post data and on-site navigation: introduces blogPosts data module, new BlogPager and BlogCTA client components, integrates them into the blog layout and replaces the local posts array in the blog index with the shared blogPosts array.

Changes

Cohort / File(s) Summary
Data Module
web/app/components/blog-posts.ts
New export blogPosts: array of post objects (slug, title, date, summary).
Pager Component
web/app/components/blog-pager.tsx
New client-side BlogPager component that uses usePathname() to locate current slug in blogPosts and conditionally renders previous/next links.
CTA Component
web/app/components/blog-cta.tsx
New client-side BlogCTA component that renders DownloadButton and GitHubButton when not on /blog.
Layout Integration
web/app/blog/layout.tsx
Imports and renders <BlogCTA /> and <BlogPager /> inside the blog layout after children.
Page Integration
web/app/blog/page.tsx
Replaces local hard-coded posts array with import { blogPosts } and maps over it; container spacing adjusted (space-y-8 → space-y-4).
Removed UI
web/app/blog/show-hn-launch/page.tsx
Removed DownloadButton and GitHubButton from this page's bottom UI block.

Sequence Diagram(s)

sequenceDiagram
    participant Browser
    participant BlogLayout
    participant BlogPager
    participant BlogPostsModule
    Browser->>BlogLayout: Request /blog or /blog/{slug}
    BlogLayout->>BlogPostsModule: import blogPosts
    BlogLayout->>Browser: render children + <BlogCTA/>
    alt route is /blog/{slug}
        BlogLayout->>BlogPager: render Pager
        BlogPager->>BlogPostsModule: read blogPosts
        BlogPager->>BlogPager: find index for current slug
        BlogPager->>Browser: render prev/next links (if present)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 I hopped through posts both old and new,
stitched slugs to titles, one, two, two—woo!
A pager to guide my carrot-tale roam,
CTA snacks to bring readers home. ✨📚

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the two main changes: adding prev/next navigation to blog posts and reducing the vertical spacing in the blog index.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-blog-nav

Comment @coderabbitai help to get the list of available commands and usage tips.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7bcfa51786

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

(post) => `/blog/${post.slug}` === pathname
);
const prev = index > 0 ? blogPosts[index - 1] : null;
const next = index < blogPosts.length - 1 ? blogPosts[index + 1] : null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle unmatched pathnames before deriving next post

findIndex returns -1 for routes that are not a specific post (for example /blog), but the current next expression treats -1 < blogPosts.length - 1 as true and sets next to blogPosts[0]. This causes the pager to render on non-post pages and can show an incorrect link whenever the pathname format does not exactly match (e.g., trailing-slash variants). Add an early index === -1 guard before computing prev/next so unmatched routes return null.

Useful? React with 👍 / 👎.

@greptile-apps

greptile-apps Bot commented Mar 4, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds prev/next navigation links to blog posts and refactors blog posts array into a shared module. The spacing on the blog index was tightened from space-y-8 to space-y-4.

  • Extracted blogPosts array from page.tsx into shared blog-posts.ts module
  • Created new BlogPager component with prev/next navigation
  • Integrated pager into blog layout so it appears on all blog post pages
  • Issue: BlogPager has logic bug where it incorrectly shows navigation on the blog index page when findIndex returns -1

Confidence Score: 3/5

  • Safe to merge after fixing the BlogPager logic bug
  • Clean refactoring with good code organization, but the BlogPager component has a logic error that causes incorrect behavior on the index page when pathname doesn't match any post
  • web/app/components/blog-pager.tsx needs the index check fix before merging

Important Files Changed

Filename Overview
web/app/components/blog-pager.tsx New prev/next navigation component with logic bug when pathname doesn't match any post
web/app/components/blog-posts.ts Extracted shared blog posts array - clean refactoring
web/app/blog/layout.tsx Added BlogPager component to layout
web/app/blog/page.tsx Refactored to use shared blogPosts array and reduced spacing from space-y-8 to space-y-4

Last reviewed commit: 7bcfa51

Comment on lines +9 to +15
const index = blogPosts.findIndex(
(post) => `/blog/${post.slug}` === pathname
);
const prev = index > 0 ? blogPosts[index - 1] : null;
const next = index < blogPosts.length - 1 ? blogPosts[index + 1] : null;

if (!prev && !next) return null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pager shows incorrectly on index page when findIndex returns -1

Suggested change
const index = blogPosts.findIndex(
(post) => `/blog/${post.slug}` === pathname
);
const prev = index > 0 ? blogPosts[index - 1] : null;
const next = index < blogPosts.length - 1 ? blogPosts[index + 1] : null;
if (!prev && !next) return null;
const index = blogPosts.findIndex(
(post) => `/blog/${post.slug}` === pathname
);
if (index === -1) return null;
const prev = index > 0 ? blogPosts[index - 1] : null;
const next = index < blogPosts.length - 1 ? blogPosts[index + 1] : null;

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 `@web/app/components/blog-pager.tsx`:
- Around line 9-15: After computing index with blogPosts.findIndex((post) =>
`/blog/${post.slug}` === pathname) in blog-pager.tsx, guard the unmatched-path
case by returning early when index === -1 so you don't derive prev/next for
non-post routes; specifically, after computing index check "if (index === -1)
return null" before computing prev and next (which reference blogPosts[index -
1] and blogPosts[index + 1]).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b5789aec-88a8-42ac-9508-9199a067d630

📥 Commits

Reviewing files that changed from the base of the PR and between 6638435 and 7bcfa51.

📒 Files selected for processing (4)
  • web/app/blog/layout.tsx
  • web/app/blog/page.tsx
  • web/app/components/blog-pager.tsx
  • web/app/components/blog-posts.ts

Comment on lines +9 to +15
const index = blogPosts.findIndex(
(post) => `/blog/${post.slug}` === pathname
);
const prev = index > 0 ? blogPosts[index - 1] : null;
const next = index < blogPosts.length - 1 ? blogPosts[index + 1] : null;

if (!prev && !next) return null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard unmatched paths before deriving neighbors.

When findIndex returns -1 (e.g., on /blog), Line 13 resolves next to the first post, so the pager renders where it shouldn't. Add an early return for no-match routes.

💡 Suggested fix
 export function BlogPager() {
   const pathname = usePathname();
   const index = blogPosts.findIndex(
     (post) => `/blog/${post.slug}` === pathname
   );
+  if (index === -1) return null;
+
   const prev = index > 0 ? blogPosts[index - 1] : null;
   const next = index < blogPosts.length - 1 ? blogPosts[index + 1] : null;
 
   if (!prev && !next) return null;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const index = blogPosts.findIndex(
(post) => `/blog/${post.slug}` === pathname
);
const prev = index > 0 ? blogPosts[index - 1] : null;
const next = index < blogPosts.length - 1 ? blogPosts[index + 1] : null;
if (!prev && !next) return null;
export function BlogPager() {
const pathname = usePathname();
const index = blogPosts.findIndex(
(post) => `/blog/${post.slug}` === pathname
);
if (index === -1) return null;
const prev = index > 0 ? blogPosts[index - 1] : null;
const next = index < blogPosts.length - 1 ? blogPosts[index + 1] : null;
if (!prev && !next) return null;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/app/components/blog-pager.tsx` around lines 9 - 15, After computing index
with blogPosts.findIndex((post) => `/blog/${post.slug}` === pathname) in
blog-pager.tsx, guard the unmatched-path case by returning early when index ===
-1 so you don't derive prev/next for non-post routes; specifically, after
computing index check "if (index === -1) return null" before computing prev and
next (which reference blogPosts[index - 1] and blogPosts[index + 1]).

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 4 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="web/app/components/blog-pager.tsx">

<violation number="1" location="web/app/components/blog-pager.tsx:13">
P2: Guard against `findIndex` returning `-1`; otherwise a non-matching pathname incorrectly shows the first post as `next`.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

(post) => `/blog/${post.slug}` === pathname
);
const prev = index > 0 ? blogPosts[index - 1] : null;
const next = index < blogPosts.length - 1 ? blogPosts[index + 1] : null;

@cubic-dev-ai cubic-dev-ai Bot Mar 4, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Guard against findIndex returning -1; otherwise a non-matching pathname incorrectly shows the first post as next.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At web/app/components/blog-pager.tsx, line 13:

<comment>Guard against `findIndex` returning `-1`; otherwise a non-matching pathname incorrectly shows the first post as `next`.</comment>

<file context>
@@ -0,0 +1,43 @@
+    (post) => `/blog/${post.slug}` === pathname
+  );
+  const prev = index > 0 ? blogPosts[index - 1] : null;
+  const next = index < blogPosts.length - 1 ? blogPosts[index + 1] : null;
+
+  if (!prev && !next) return null;
</file context>
Suggested change
const next = index < blogPosts.length - 1 ? blogPosts[index + 1] : null;
const next = index >= 0 && index < blogPosts.length - 1 ? blogPosts[index + 1] : null;
Fix with Cubic

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 `@web/app/components/blog-cta.tsx`:
- Around line 13-14: The GitHubButton component currently hardcodes its
analytics location as "hero", causing misattribution; update the usage in
blog-cta.tsx to pass the same location prop as DownloadButton
(location="blog-bottom") and update the GitHubButton component
(github-button.tsx) to accept a location prop (e.g., prop name location) with a
sensible default ("hero") and use that prop when emitting analytics events in
the click handler (e.g., inside the function handling clicks), so blog-footer
clicks report "blog-bottom" while preserving existing behavior elsewhere.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 624d290b-c7ea-4cc9-ad75-f10093176b08

📥 Commits

Reviewing files that changed from the base of the PR and between 7bcfa51 and 1d67936.

📒 Files selected for processing (3)
  • web/app/blog/layout.tsx
  • web/app/blog/show-hn-launch/page.tsx
  • web/app/components/blog-cta.tsx
💤 Files with no reviewable changes (1)
  • web/app/blog/show-hn-launch/page.tsx

Comment thread web/app/components/blog-cta.tsx Outdated
Comment on lines +13 to +14
<DownloadButton location="blog-bottom" />
<GitHubButton />

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix GitHub CTA analytics location attribution.

DownloadButton is tagged with location="blog-bottom", but GitHubButton still tracks clicks as "hero" (hardcoded in web/app/components/github-button.tsx), so blog-footer clicks are misattributed.

Proposed fix
diff --git a/web/app/components/github-button.tsx b/web/app/components/github-button.tsx
@@
-export function GitHubButton() {
+export function GitHubButton({ location = "hero" }: { location?: string }) {
@@
-      onClick={() => posthog.capture("cmuxterm_github_clicked", { location: "hero" })}
+      onClick={() => posthog.capture("cmuxterm_github_clicked", { location })}
diff --git a/web/app/components/blog-cta.tsx b/web/app/components/blog-cta.tsx
@@
-      <GitHubButton />
+      <GitHubButton location="blog-bottom" />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<DownloadButton location="blog-bottom" />
<GitHubButton />
<DownloadButton location="blog-bottom" />
<GitHubButton location="blog-bottom" />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/app/components/blog-cta.tsx` around lines 13 - 14, The GitHubButton
component currently hardcodes its analytics location as "hero", causing
misattribution; update the usage in blog-cta.tsx to pass the same location prop
as DownloadButton (location="blog-bottom") and update the GitHubButton component
(github-button.tsx) to accept a location prop (e.g., prop name location) with a
sensible default ("hero") and use that prop when emitting analytics events in
the click handler (e.g., inside the function handling clicks), so blog-footer
clicks report "blog-bottom" while preserving existing behavior elsewhere.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d7f41915ce

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +9 to +11
if (pathname === "/blog") return null;

const slug = pathname.replace("/blog/", "");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Normalize blog index pathname before showing CTA

The index guard only checks "/blog", so visiting the valid trailing-slash variant (/blog/) falls through and renders the CTA with location set to "blog-". Because this app has skipTrailingSlashRedirect: true in web/next.config.ts, that URL is not automatically canonicalized, so users can hit this path and see post-only CTA controls on the index while analytics events are mislabeled. Treat both /blog and /blog/ (or normalize pathname) before deriving the slug.

Useful? React with 👍 / 👎.

@lawrencecchen
lawrencecchen merged commit f5de515 into main Mar 4, 2026
12 checks passed
@lawrencecchen
lawrencecchen deleted the fix-blog-nav branch March 4, 2026 09:52
@coderabbitai coderabbitai Bot mentioned this pull request Mar 31, 2026
3 tasks
bn-l pushed a commit to bn-l/cmux that referenced this pull request Apr 3, 2026
* Add prev/next navigation to blog posts, reduce index gap

* Add download/GitHub CTA to all blog posts via layout

* Track blog post slug in PostHog download/GitHub click events

This branch was successfully deployed

1 active deployment
Preview — d7f41915 Deployed Mar 4, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant