Conversation
- Updated blog directory path to use process.cwd() for better content management. - Introduced blog schema validation using Zod for frontmatter. - Added a new layout component for blog pages to enhance styling. - Refactored blog index and post pages to utilize dynamic imports and improve metadata handling. - Implemented functions to retrieve blog data and handle slugs more effectively. - Migrated example blog post to new content directory structure and created a new index file for blog updates.
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
📝 WalkthroughWalkthroughThe pull request migrates the blog system from filesystem-based markdown loading to Nextra-driven dynamic imports. Changes include adding Zod schema validation for blog frontmatter, restructuring blog data access with a new Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Client Request
participant PageComponent as Blog Page Component
participant Nextra as Nextra importPage()
participant BlogLoader as readBlogs (getBlogBySlug)
participant Schema as Zod Schema
participant Renderer as MDX Renderer
Client->>PageComponent: Request /blog/[slug]
PageComponent->>Nextra: importPage(slug)
Nextra-->>PageComponent: Returns MDXContent, metadata, sourceCode
PageComponent->>BlogLoader: getBlogBySlug(slug)
BlogLoader->>Schema: validate(frontmatter)
Schema-->>BlogLoader: BlogData or null
BlogLoader-->>PageComponent: Blog data (title, date, author, etc.)
PageComponent->>PageComponent: Merge Nextra metadata + Blog data
PageComponent->>PageComponent: Check if sourceCode contains H1
PageComponent->>Renderer: Render MDXContent
Renderer-->>PageComponent: Rendered HTML
PageComponent->>Client: Page with conditionally rendered title + content
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. 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 |
| export const BlogFrontmatterSchema = z.object({ | ||
| title: z.string(), | ||
| published: z.boolean().default(false), | ||
| date: z.string(), |
There was a problem hiding this comment.
The date field is validated as a generic string but is later used for date parsing and sorting. Invalid date strings (e.g., "not-a-date") will pass validation but cause NaN values when parsed with new Date() in the sort function (readBlogs.tsx:43-44), leading to unpredictable sort order.
date: z.string().refine(
(val) => !isNaN(Date.parse(val)),
{ message: "Must be a valid date string" }
),| date: z.string(), | |
| date: z.string().refine((val) => !isNaN(Date.parse(val)), { message: "Must be a valid date string" }), |
Spotted by Graphite
Is this helpful? React 👍 or 👎 to let us know.
| const hasPrimaryHeading = (sourceCode: string): boolean => | ||
| /(^|\n)#\s+\S/m.test(sourceCode); |
There was a problem hiding this comment.
🟡 hasPrimaryHeading regex matches # inside fenced code blocks, suppressing the blog post title
The hasPrimaryHeading function uses the regex /(^|\n)#\s+\S/m to detect whether MDX source code already contains an H1 heading. However, this regex operates on raw source text and will match # at the start of any line—including inside fenced code blocks (e.g., bash/Python comments like # install deps). When a blog post has no actual H1 heading but contains a code block with a # comment line, the function returns true, and the prominent <h1> with the blog title is not rendered (apps/website/app/(home)/blog/[slug]/page.tsx:86-89). The post then appears without any visible title. The same function is duplicated in the blog index page at apps/website/app/(home)/blog/page.tsx:8-9.
Prompt for agents
The hasPrimaryHeading function in both apps/website/app/(home)/blog/[slug]/page.tsx (line 15-16) and apps/website/app/(home)/blog/page.tsx (line 8-9) uses a naive regex that matches # at the start of any line, including inside fenced code blocks. This causes false positives when a blog post contains e.g. a bash code block with a # comment but no actual H1 heading, resulting in the blog title not being rendered.
A more robust approach would be to skip content inside fenced code blocks (``` or ~~~) before testing for headings. For example, strip fenced code blocks first with a regex like /^(`{3,}|~{3,}).*\n[\s\S]*?^\1/gm, then test the remaining text. Alternatively, since Nextra compiles MDX to an AST, you could inspect the compiled output or the table of contents for an H1 rather than regex-matching raw source.
Also, this function is duplicated in two files. Per AGENTS.md guidelines (prefer util functions for reusable logic), extract it into a shared utility in the blog directory.
Was this helpful? React with 👍 or 👎 to provide feedback.
| title: z.string(), | ||
| published: z.boolean().default(false), | ||
| date: z.string(), | ||
| author: z.string(), |
There was a problem hiding this comment.
🟡 YAML null for tags (or description) silently drops the entire blog post
In YAML, a key with no value (e.g., tags: on its own line) parses as null via gray-matter/js-yaml. Zod's .optional() only accepts undefined, not null, so null falls through to BlogTagsSchema which is z.union([z.array(z.string()), z.string()]) — neither branch accepts null, causing a ZodError. This error is caught by processBlogFile at apps/website/app/(home)/blog/readBlogs.tsx:37, which returns null, silently excluding the blog post from the site. The same issue affects description: z.string().optional() — writing description: with no value in frontmatter also triggers a validation failure.
Triggering frontmatter example
---
title: "My Post"
date: "2024-01-01"
author: "Me"
published: true
tags:
---gray-matter parses tags as null. The chain .optional().default([]) only substitutes [] when the input is undefined, not null, so null reaches the union schema and fails.
Prompt for agents
In apps/website/app/(home)/blog/blogSchema.ts, the tags and description fields need to handle null values from YAML frontmatter. In YAML, a key with no value (e.g. tags: on its own line) parses as null, not undefined. Zod's .optional() only accepts undefined, so null causes validation to fail, which silently drops the blog post in processBlogFile (readBlogs.tsx:37).
For the tags field (line 13), consider adding .nullable() before .optional(), or using z.preprocess to coerce null to undefined. For example:
tags: BlogTagsSchema.nullable().optional().default([])
or
tags: z.preprocess((v) => v ?? undefined, BlogTagsSchema.optional().default([]))
Similarly, for the description field (line 12), z.string().optional() should become z.string().nullable().optional() to handle the case where description: is present in frontmatter with no value.
Was this helpful? React with 👍 or 👎 to provide feedback.
Summary by CodeRabbit
Release Notes
New Features
Documentation