feat(blog): add series support with featured shelf and per-series pages#7925
Conversation
Introduce first-class series support for the blog. Posts opt in via a
`series: <key>` + `seriesIndex` pair in frontmatter, where the key
resolves to a single source of truth in `series-registry.ts` for title,
description, and an optional `featured` flag.
The home page now surfaces series under the tag filter as a responsive
chip row plus a prominent highlight card for the featured series; the
full list lives at `/blog/series`, and each series gets its own page at
`/blog/series/<key>`. On individual posts, a small marker under the
tags states "Part N of M in <series>" and links to the series page,
with a detailed overview and prev/next navigation rendered at the
bottom of the article.
Migrates all 9 existing series off the inline `series: { title }` shape
to keys, and threads the new `prisma-next` series across the 8 posts
that make up that arc. Also fixes the AI tag rendering as "Ai" by
extending `formatTag` with an acronym map.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (14)
WalkthroughThis PR converts series frontmatter to string keys, adds a typed series registry and helpers, migrates post frontmatter and links, adds series UI components and a home shelf, and implements series index/detail pages with SEO. ChangesBlog Series Platform
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint skipped: no ESLint configuration detected in root package.json. To enable, add Comment |
|
The latest updates on your projects. Learn more about Argos notifications ↗︎
|
…series" link Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 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/blog/content/blog/prisma-orm-without-rust-latest-performance-benchmarks/index.mdx`:
- Line 11: The frontmatter series key was changed to
"rust-to-typescript-migration-journey" but the internal series link in the post
body still points to the old slug; update the link target on the post (the link
referenced around line 29) from
"/blog/series/prisma-orm-the-complete-rust-to-typescript-migration-journey" to
the key-based path "/blog/series/rust-to-typescript-migration-journey" so the
series route matches the new frontmatter key.
In
`@apps/blog/content/blog/rust-to-typescript-update-boosting-prisma-orm-performance/index.mdx`:
- Line 12: The article's frontmatter sets series:
rust-to-typescript-migration-journey but the in-article series link still uses
the old slug; update the in-article series URL/slug to match the frontmatter
value (replace the old series slug string with
"rust-to-typescript-migration-journey") so the link points to the migrated
series key referenced by the series field.
In `@apps/blog/src/components/SeriesShelf.tsx`:
- Around line 110-113: The current construction of chips can exceed CHIP_LIMIT
when featured has more than the expected count because only nonFeatured is
sliced; replace the concatenation logic so you first combine featured and
nonFeatured (using the existing arrays) and then slice the resulting array to
CHIP_LIMIT (e.g., take the first CHIP_LIMIT items), ensuring the final chips
array never exceeds CHIP_LIMIT; update the code that defines chips (referencing
chips, featured, nonFeatured, and CHIP_LIMIT) to perform the slice on the
combined array instead of slicing only nonFeatured.
In `@apps/blog/src/lib/format.ts`:
- Line 19: Replace the prototype-inclusive "in" check with an own-property check
when looking up acronyms: instead of using "tag in TAG_ACRONYMS" (which can
match prototype keys), call an own-property guard like
Object.prototype.hasOwnProperty.call(TAG_ACRONYMS, tag) or
Object.hasOwn(TAG_ACRONYMS, tag) and only then return TAG_ACRONYMS[tag]; keep
the same TAG_ACRONYMS and tag identifiers so the change is localized to that
lookup.
In `@apps/blog/src/lib/series-registry.ts`:
- Around line 80-89: The current checks in getSeriesMetadata and
isKnownSeriesKey use the `in` operator which can match prototype properties;
change both to use an own-property check (e.g.,
Object.prototype.hasOwnProperty.call(seriesRegistry, key) or
Object.hasOwn(seriesRegistry, key) depending on your TS target) so only true
keys in seriesRegistry are treated as known—update the check in
getSeriesMetadata (before casting/returning entry) and in isKnownSeriesKey to
use the chosen own-property test.
In `@apps/blog/src/lib/series.ts`:
- Around line 86-95: The explicit prev/next resolution
(explicitPrev/explicitNext via findBySlug) must be constrained to the same
series to avoid jumping out of the current series: after resolving
explicitPrev/explicitNext, verify the resolved post's series matches the current
post's series (e.g. derive currentSeries from posts[currentIndex].series or
data.series) and only assign to prevPage/nextPage if the series matches;
otherwise fall back to the positionalPrev/positionalNext. Update the logic
around findBySlug, explicitPrev, explicitNext, prevPage and nextPage to perform
this series-equality check before using the explicit links.
🪄 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
Run ID: 5c6464e4-86b8-417e-a055-c9d570e007c9
📒 Files selected for processing (61)
apps/blog/content/blog/backend-prisma-typescript-orm-with-postgresql-auth-mngp1ps7kip4/index.mdxapps/blog/content/blog/backend-prisma-typescript-orm-with-postgresql-data-modeling-tsjs1ps7kip1/index.mdxapps/blog/content/blog/backend-prisma-typescript-orm-with-postgresql-deployment-bbba1ps7kip5/index.mdxapps/blog/content/blog/backend-prisma-typescript-orm-with-postgresql-rest-api-validation-dcba1ps7kip3/index.mdxapps/blog/content/blog/data-migrations-in-prisma-next/index.mdxapps/blog/content/blog/e2e-type-safety-graphql-react-1-I2GxIfxkSZ/index.mdxapps/blog/content/blog/e2e-type-safety-graphql-react-2-j9mEyHY0Ej/index.mdxapps/blog/content/blog/e2e-type-safety-graphql-react-3-fbV2ZVIGWg/index.mdxapps/blog/content/blog/e2e-type-safety-graphql-react-4-JaHA8GbkER/index.mdxapps/blog/content/blog/from-rust-to-typescript-a-new-chapter-for-prisma-orm/index.mdxapps/blog/content/blog/fullstack-nextjs-graphql-prisma-2-fwpc6ds155/index.mdxapps/blog/content/blog/fullstack-nextjs-graphql-prisma-3-clxbrcqppv/index.mdxapps/blog/content/blog/fullstack-nextjs-graphql-prisma-4-1k1kc83x3v/index.mdxapps/blog/content/blog/fullstack-nextjs-graphql-prisma-5-m2fna60h7c/index.mdxapps/blog/content/blog/fullstack-nextjs-graphql-prisma-oklidw1rhw/index.mdxapps/blog/content/blog/fullstack-remix-prisma-mongodb-1-7d0bftxbmb6r/index.mdxapps/blog/content/blog/fullstack-remix-prisma-mongodb-2-ZTmOy58p4re8/index.mdxapps/blog/content/blog/fullstack-remix-prisma-mongodb-3-By5pmN5Nzo1v/index.mdxapps/blog/content/blog/fullstack-remix-prisma-mongodb-4-l3mwep4zlim2/index.mdxapps/blog/content/blog/fullstack-remix-prisma-mongodb-5-gOhQsnfUPXSx/index.mdxapps/blog/content/blog/improving-query-performance-using-indexes-1-zuLNZwBkuL/index.mdxapps/blog/content/blog/improving-query-performance-using-indexes-2-MyoiJNMFTsfq/index.mdxapps/blog/content/blog/improving-query-performance-using-indexes-3-kduk351qv1/index.mdxapps/blog/content/blog/introducing-graphql-nexus-code-first-graphql-server-development-ll6s1yy5cxl5/index.mdxapps/blog/content/blog/nestjs-prisma-authentication-7D056s1s0k3l/index.mdxapps/blog/content/blog/nestjs-prisma-error-handling-7D056s1kOop2/index.mdxapps/blog/content/blog/nestjs-prisma-relational-data-7D056s1kOabc/index.mdxapps/blog/content/blog/nestjs-prisma-rest-api-7D056s1BmOL0/index.mdxapps/blog/content/blog/nestjs-prisma-validation-7D056s1kOla1/index.mdxapps/blog/content/blog/prisma-6-9-0-release/index.mdxapps/blog/content/blog/prisma-next-call-for-extension-authors/index.mdxapps/blog/content/blog/prisma-next-early-access-write-your-contract-prompt-your-agent-ship-your-app/index.mdxapps/blog/content/blog/prisma-next-roadmap-april-milestone/index.mdxapps/blog/content/blog/prisma-next-roadmap/index.mdxapps/blog/content/blog/prisma-orm-without-rust-latest-performance-benchmarks/index.mdxapps/blog/content/blog/rethinking-database-migrations/index.mdxapps/blog/content/blog/rust-free-prisma-orm-is-ready-for-production/index.mdxapps/blog/content/blog/rust-to-typescript-update-boosting-prisma-orm-performance/index.mdxapps/blog/content/blog/testing-series-1-8eRB5p0Y8o/index.mdxapps/blog/content/blog/testing-series-2-xPhjjmIEsM/index.mdxapps/blog/content/blog/testing-series-3-aBUyF8nxAn/index.mdxapps/blog/content/blog/testing-series-4-OVXtDis201/index.mdxapps/blog/content/blog/testing-series-5-xWogenROXm/index.mdxapps/blog/content/blog/the-next-evolution-of-prisma-orm/index.mdxapps/blog/content/blog/the-problems-of-schema-first-graphql-development-x1mn4cb0tyl3/index.mdxapps/blog/content/blog/try-the-new-rust-free-version-of-prisma-orm-early-access/index.mdxapps/blog/content/blog/typescript-migrations-in-prisma-next/index.mdxapps/blog/content/blog/using-graphql-nexus-with-a-database-pmyl3660ncst/index.mdxapps/blog/source.config.tsapps/blog/src/app/(blog)/[slug]/page.tsxapps/blog/src/app/(blog)/page.tsxapps/blog/src/app/(blog)/series/[key]/page.tsxapps/blog/src/app/(blog)/series/page.tsxapps/blog/src/components/BlogHomeClient.tsxapps/blog/src/components/SeriesBanner.tsxapps/blog/src/components/SeriesMarker.tsxapps/blog/src/components/SeriesNavigation.tsxapps/blog/src/components/SeriesShelf.tsxapps/blog/src/lib/format.tsapps/blog/src/lib/series-registry.tsapps/blog/src/lib/series.ts
|
Good stuff 🚀 |
Summary
series: <key>+seriesIndexin frontmatter, with title, description, and an optionalfeaturedflag defined once inapps/blog/src/lib/series-registry.ts./blog/series, and each series gets its own page at/blog/series/<key>.series: { title }shape onto keys, and adds a newprisma-nextseries across 8 posts.Aiby extendingformatTagwith a small acronym map.How to add a post to a series
In the post's frontmatter:
Prev/next are derived automatically from
seriesIndex. Override per-post withprev:/next:(slug strings) if needed.How to add a new series
Add a key in
apps/blog/src/lib/series-registry.ts:Then set
series: my-keyandseriesIndex: Non each post.Test plan
/blog/serieslists every series, featured first with the brand-accented border./blog/series/prisma-next(and every other key) renders the series description and all its posts in order./blog/rethinking-database-migrations), the marker under the tags shows the correct "Part N of M", the bottom overview lists every post with the current one markedReading, and the prev/next cards point to the right neighbours./blog/series/does-not-existreturns 404./blog/agentic-engineering-at-prisma) shows the tag asAI, notAi.pnpm types:checkfromapps/blogpasses.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Chores