feat(marketing): add blog system with MDX support#929
Conversation
- Add blog listing page with grid background and category tags - Add blog post page with Better Auth-inspired layout - Implement MDX rendering with gray-matter and next-mdx-remote - Add CodeBlock component from ai-elements for syntax highlighting - Include sample blog posts (Introducing Superset, Parallel Agents Guide) - Style matches landing page aesthetic with mono fonts and hard borders
📝 WalkthroughWalkthroughAdds MDX-based blog content, blog data utilities, new UI components/layouts for posts, MDX rendering with syntax-highlighted code blocks and copy buttons, prose styling, and small package dependency updates for MDX and syntax highlighting. Changes
Sequence Diagram(s)sequenceDiagram
participant Browser as Browser
participant NextPage as BlogPostPage
participant FS as FileSystem
participant Parser as gray-matter / blog parser
participant MDX as MDXRemote
participant Components as mdxComponents
participant Layout as BlogPostLayout
Browser->>NextPage: GET /blog/{slug}
NextPage->>FS: read `content/blog/{slug}.mdx`
FS-->>Parser: return frontmatter + raw content
Parser-->>NextPage: BlogPost { frontmatter, content }
NextPage->>MDX: render content with mdxComponents
MDX->>Components: render headings, code blocks, images, video
Components-->>MDX: highlighted code + copy button, slugged anchors
MDX->>Layout: rendered nodes + TOC
Layout-->>Browser: HTML response (hero, content, footer)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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 |
- Add GridCross component for intersection markers - Add vertical guide lines on content edges - Use border sections to separate header, content, footer - Add shiki dependency for syntax highlighting types
🧹 Preview Cleanup CompleteThe following preview resources have been cleaned up:
Thank you for your contribution! 🎉 |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@apps/marketing/src/app/blog/`[slug]/page.tsx:
- Around line 91-97: The BlogPostLayoutProps declares a toc prop that is passed
from the page (extractToc in page.tsx) but BlogPostLayout only destructures {
post, children }, so toc is unused; update the BlogPostLayout function signature
to also accept and use toc (e.g., destructure { post, children, toc } and render
or forward it to the table-of-contents UI), or if you prefer to remove the
feature, delete toc from BlogPostLayoutProps and stop passing toc from the page
where extractToc(post.content) is called. Target the BlogPostLayout function and
BlogPostLayoutProps types and the page component that calls <BlogPostLayout
post={post} toc={toc}> to keep the prop and usage consistent.
- Around line 30-40: The MDX language extraction currently force-casts arbitrary
strings to BundledLanguage (see codeProps/className -> match -> language) which
lets unsupported values reach shiki and cause unhandled promise rejections in
CodeBlock; fix by validating the extracted language against the supported
shiki/BundledLanguage set before casting (map unknown values to "text") or by
adding error handling around CodeBlock's highlightCode() call (in the CodeBlock
component's useEffect) to catch shiki/codeToHtml errors and fallback to
plaintext rendering, ensuring you reference the extracted variable named
language and the highlightCode()/codeToHtml call sites when implementing the
fix.
In `@apps/marketing/src/lib/blog.ts`:
- Around line 49-51: The catch block that currently swallows errors and returns
null should log the caught error with context before returning; modify the catch
to capture the error (e) and call your logger (e.g., processLogger.error or
console.error) with a message indicating which operation failed and include the
error object, then return null as before so failures are observable during
debugging.
🧹 Nitpick comments (6)
apps/marketing/src/app/globals.css (1)
25-105: Consider extracting repeated color values to CSS variables.The
rgb(255 255 255 / ...)pattern is repeated throughout. For easier maintenance and theme consistency, consider defining these at the top with the other prose variables.♻️ Optional refactor example
.prose { + --prose-border: rgb(255 255 255 / 0.1); + --prose-bg-subtle: rgb(255 255 255 / 0.05); --tw-prose-body: rgb(255 255 255 / 0.7); /* ... */ } .prose pre { - background: rgb(255 255 255 / 0.05); - border: 1px solid rgb(255 255 255 / 0.1); + background: var(--prose-bg-subtle); + border: 1px solid var(--prose-border); /* ... */ }apps/marketing/src/app/blog/page.tsx (2)
11-18: ExtractGridCrossto a shared component.This component is duplicated in
BlogPostLayout.tsx(lines 20-27) and violates the one-component-per-file guideline. Extract it to a shared location like@/components/GridCrossto ensure consistency and reduce duplication.
26-33: Extract magic numbers and shared gradient style.The
384pxand383pxvalues appear as magic numbers. Per coding guidelines, extract these to named constants. This gradient is also duplicated inBlogPostLayout.tsx—consider extracting to a shared utility or CSS class.Proposed approach
// At module top or in a shared constants file const CONTENT_WIDTH = 384; const GUIDE_LINE_GRADIENT = ` linear-gradient(to right, transparent 0%, transparent calc(50% - ${CONTENT_WIDTH}px), rgba(255,255,255,0.06) calc(50% - ${CONTENT_WIDTH}px), rgba(255,255,255,0.06) calc(50% - ${CONTENT_WIDTH - 1}px), transparent calc(50% - ${CONTENT_WIDTH - 1}px), transparent calc(50% + ${CONTENT_WIDTH - 1}px), rgba(255,255,255,0.06) calc(50% + ${CONTENT_WIDTH - 1}px), rgba(255,255,255,0.06) calc(50% + ${CONTENT_WIDTH}px), transparent calc(50% + ${CONTENT_WIDTH}px) ) `;apps/marketing/src/app/blog/[slug]/components/BlogPostLayout/BlogPostLayout.tsx (2)
8-18: DuplicateTocItemtype and unusedtocprop.
TocItemis already exported from@/lib/blog—import it instead of redefining.- The
tocprop is declared but never used in the component. Either implement TOC rendering or remove the prop.Proposed fix
import type { BlogPost } from "@/lib/blog"; +import type { TocItem } from "@/lib/blog"; -interface TocItem { - id: string; - text: string; - level: number; -} - interface BlogPostLayoutProps { post: BlogPost; - toc: TocItem[]; + toc?: TocItem[]; // Make optional until implemented, or remove if not needed children: ReactNode; }
20-27: DuplicateGridCrosscomponent.This is identical to the
GridCrossinapps/marketing/src/app/blog/page.tsx. Extract to a shared component to maintain DRY principles and the one-component-per-file guideline.apps/marketing/src/app/blog/[slug]/page.tsx (1)
13-18: Duplicate slug generation logic withextractTocin blog.ts.This
slugifyfunction is identical to the ID generation logic inextractToc(lines 108-114 inblog.ts). If either implementation changes independently, TOC anchor links will break.Consider extracting to a shared utility in
blog.tsand reusing it in both places.♻️ Suggested refactor
In
apps/marketing/src/lib/blog.ts, export aslugifyfunction:export function slugify(text: string): string { return text .toLowerCase() .replace(/[^a-z0-9]+/g, "-") .replace(/(^-|-$)/g, ""); }Then update
extractTocto use it, and import it here:-import { getAllSlugs, getBlogPost, extractToc } from "@/lib/blog"; +import { getAllSlugs, getBlogPost, extractToc, slugify } from "@/lib/blog"; - -function slugify(text: string): string { - return text - .toLowerCase() - .replace(/[^a-z0-9]+/g, "-") - .replace(/(^-|-$)/g, ""); -}
| const codeProps = children.props as { | ||
| children?: string; | ||
| className?: string; | ||
| }; | ||
| const code = codeProps.children?.trim() ?? ""; | ||
| const className = codeProps.className ?? ""; | ||
| const match = className.match(/language-(\w+)/); | ||
| const language = (match?.[1] ?? "text") as BundledLanguage; | ||
| return { code, language }; | ||
| } | ||
| return { code: String(children ?? ""), language: "text" as BundledLanguage }; |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
shiki unknown language identifier fallback graceful handling
💡 Result:
To handle an unknown / unsupported Shiki language id gracefully, do one (or combine) of these patterns:
- Fallback to plain text (
text, aliases:plain,txt)
import { codeToHtml } from 'shiki'
const safeLang = (lang?: string) => lang || 'text' // or your own validation
const html = await codeToHtml(code, { lang: safeLang(userLang), theme: 'vitesse-dark' })Shiki explicitly supports lang: 'text' as a “no highlighting” fallback. [1]
- Try/catch and fallback
import { codeToHtml } from 'shiki'
let html: string
try {
html = await codeToHtml(code, { lang: userLang, theme: 'vitesse-dark' })
} catch {
html = await codeToHtml(code, { lang: 'text', theme: 'vitesse-dark' })
}Catching highlighting errors is a supported/expected approach. [2]
- If you use a
highlighterinstance: load-or-fallback
If you created a highlighter with only some languages, Shiki will throw when you request a language that isn’t loaded; you canloadLanguage()or fallback totext. [3]
import { createHighlighter } from 'shiki'
const highlighter = await createHighlighter({ themes: ['vitesse-dark'], langs: ['js'] })
async function highlight(code: string, lang: string) {
try {
await highlighter.loadLanguage(lang) // load if available as a bundled lang
return highlighter.codeToHtml(code, { lang, theme: 'vitesse-dark' })
} catch {
return highlighter.codeToHtml(code, { lang: 'text', theme: 'vitesse-dark' })
}
}- Map “unknown” ids via
langAlias
If your users pass non-standard ids (e.g.mylang,tsxreact, etc.), map them to a real Shiki id:
const highlighter = await createHighlighter({
themes: ['nord'],
langs: ['javascript'],
langAlias: { mylang: 'javascript' },
})Shiki supports langAlias for this. [4]
Sources: Shiki languages docs (plain-text fallback) [1], Shiki error-handling example [2], Shiki install/usage docs (throws when language not loaded; loadLanguage) [3], Shiki custom language docs (langAlias) [4].
🏁 Script executed:
cat -n apps/marketing/src/app/blog/[slug]/page.tsx | head -50Repository: superset-sh/superset
Length of output: 1864
🏁 Script executed:
rg -A 10 "extractCodeFromChildren" apps/marketing/src/app/blog/[slug]/page.tsxRepository: superset-sh/superset
Length of output: 706
🏁 Script executed:
fd CodeBlock --type f | head -10Repository: superset-sh/superset
Length of output: 152
🏁 Script executed:
rg -B 5 -A 15 "interface.*code.*language\|type.*code.*language" apps/marketing/src --type tsx --type ts | head -60Repository: superset-sh/superset
Length of output: 91
🏁 Script executed:
cat -n apps/desktop/src/renderer/components/MarkdownRenderer/components/CodeBlock/CodeBlock.tsx | head -100Repository: superset-sh/superset
Length of output: 1693
🏁 Script executed:
find apps -name "CodeBlock*" -type f | head -20Repository: superset-sh/superset
Length of output: 152
🏁 Script executed:
rg "language.*BundledLanguage\|CodeBlock.*language" apps/marketing --type ts -A 3 -B 3Repository: superset-sh/superset
Length of output: 46
🏁 Script executed:
find . -path "*/ai-elements/code-block*" -type f | head -20Repository: superset-sh/superset
Length of output: 119
🏁 Script executed:
rg "export.*CodeBlock" --type ts --type tsx | grep -v node_modules | head -20Repository: superset-sh/superset
Length of output: 91
🏁 Script executed:
fd "code-block" --type f | grep -v node_modules | head -20Repository: superset-sh/superset
Length of output: 117
🏁 Script executed:
cat -n packages/ui/src/components/ai-elements/code-block.tsxRepository: superset-sh/superset
Length of output: 5402
Unsafe type assertion for BundledLanguage causes silent rendering failures with unsupported languages.
The as BundledLanguage cast on lines 37 and 40 bypasses type checking, allowing invalid language identifiers to reach runtime. When an unsupported language is extracted from MDX (e.g., language-foobar), shiki's codeToHtml() will throw an error. However, the CodeBlock component (packages/ui/src/components/ai-elements/code-block.tsx) has no error handling in its useEffect promise chain (lines 87-94), causing unhandled promise rejections and silent failures where code blocks simply don't render.
Either validate the extracted language against a set of supported languages before passing it to CodeBlock, or add error handling in CodeBlock's highlightCode() call to catch shiki errors and fallback to plaintext rendering.
🤖 Prompt for AI Agents
In `@apps/marketing/src/app/blog/`[slug]/page.tsx around lines 30 - 40, The MDX
language extraction currently force-casts arbitrary strings to BundledLanguage
(see codeProps/className -> match -> language) which lets unsupported values
reach shiki and cause unhandled promise rejections in CodeBlock; fix by
validating the extracted language against the supported shiki/BundledLanguage
set before casting (map unknown values to "text") or by adding error handling
around CodeBlock's highlightCode() call (in the CodeBlock component's useEffect)
to catch shiki/codeToHtml errors and fallback to plaintext rendering, ensuring
you reference the extracted variable named language and the
highlightCode()/codeToHtml call sites when implementing the fix.
| const toc = extractToc(post.content); | ||
|
|
||
| return ( | ||
| <main> | ||
| <BlogPostLayout post={post} toc={toc}> | ||
| <MDXRemote source={post.content} components={components} /> | ||
| </BlogPostLayout> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Find the BlogPostLayout component file
fd -t f "BlogPostLayout" apps/marketing/src/Repository: superset-sh/superset
Length of output: 143
🏁 Script executed:
# Search for BlogPostLayoutProps interface and toc references
rg "BlogPostLayoutProps|interface.*BlogPostLayout" apps/marketing/src/ -A 10Repository: superset-sh/superset
Length of output: 2518
🏁 Script executed:
# Search for toc usage in the BlogPostLayout directory
rg "toc" apps/marketing/src/app/blog/ -nRepository: superset-sh/superset
Length of output: 338
Fix unused toc prop in BlogPostLayout component.
The toc prop is defined in BlogPostLayoutProps and passed from the page component, but the BlogPostLayout function signature only destructures { post, children }, leaving toc unused. Either destructure and use toc in the component implementation or remove it from the interface and stop passing it.
🤖 Prompt for AI Agents
In `@apps/marketing/src/app/blog/`[slug]/page.tsx around lines 91 - 97, The
BlogPostLayoutProps declares a toc prop that is passed from the page (extractToc
in page.tsx) but BlogPostLayout only destructures { post, children }, so toc is
unused; update the BlogPostLayout function signature to also accept and use toc
(e.g., destructure { post, children, toc } and render or forward it to the
table-of-contents UI), or if you prefer to remove the feature, delete toc from
BlogPostLayoutProps and stop passing toc from the page where
extractToc(post.content) is called. Target the BlogPostLayout function and
BlogPostLayoutProps types and the page component that calls <BlogPostLayout
post={post} toc={toc}> to keep the prop and usage consistent.
| } catch { | ||
| return null; | ||
| } |
There was a problem hiding this comment.
Silent error swallowing in catch block.
The empty catch block discards error information, making debugging difficult. Per coding guidelines, errors should at minimum be logged with context.
Proposed fix
- } catch {
+ } catch (error) {
+ console.error(`[blog/parseFrontmatter] Failed to parse ${filePath}:`, error);
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.
| } catch { | |
| return null; | |
| } | |
| } catch (error) { | |
| console.error(`[blog/parseFrontmatter] Failed to parse ${filePath}:`, error); | |
| return null; | |
| } |
🤖 Prompt for AI Agents
In `@apps/marketing/src/lib/blog.ts` around lines 49 - 51, The catch block that
currently swallows errors and returns null should log the caught error with
context before returning; modify the catch to capture the error (e) and call
your logger (e.g., processLogger.error or console.error) with a message
indicating which operation failed and include the error object, then return null
as before so failures are observable during debugging.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In
`@apps/marketing/src/app/blog/`[slug]/components/BlogPostLayout/BlogPostLayout.tsx:
- Around line 8-18: The TocItem interface and the toc prop on
BlogPostLayoutProps are unused; either implement the TOC or remove the dead
code—if you’re not adding a table of contents, delete the TocItem interface and
remove toc from BlogPostLayoutProps and any references to toc in the
BlogPostLayout component (look for BlogPostLayoutProps, TocItem, and the
BlogPostLayout component declaration) so the prop and type are no longer
declared but the component still accepts post and children.
🧹 Nitpick comments (3)
apps/marketing/src/app/blog/[slug]/components/BlogPostLayout/BlogPostLayout.tsx (3)
1-1: Consider removing"use client"directive.This component doesn't use any client-side hooks (useState, useEffect, etc.) or event handlers with state. It only performs static rendering and date formatting, both of which work server-side. Making this a Server Component would improve performance by reducing the client bundle.
Suggested change
-"use client"; - import { ArrowLeft } from "lucide-react";
20-42: Multiple components in single file.Per coding guidelines,
.tsxfiles should have one component per file.GridCrossandAuthorAvatarare helper components defined alongsideBlogPostLayout. Consider extracting them to separate files (e.g.,GridCross.tsx,AuthorAvatar.tsx) or a sharedcomponents/decorativedirectory if reused elsewhere.
54-61: Extract magic numbers to named constants.The values
384and383are repeated and not self-documenting. Per coding guidelines, extract hardcoded values to named constants at module top for clarity and maintainability.Suggested refactor
+const GRID_LINE_OFFSET_PX = 384; +const GRID_LINE_INNER_OFFSET_PX = 383; + export function BlogPostLayout({ post, children }: BlogPostLayoutProps) {Then use template literals or a helper to construct the background image, or add a comment explaining these values represent half the container width (768px / 2).
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@apps/marketing/src/app/blog/components/AuthorAvatar/AuthorAvatar.tsx`:
- Around line 31-36: The initials computation can produce undefined when name
has extra spaces; update the initials logic (the initials constant in
AuthorAvatar) to normalize and filter segments before taking first chars — e.g.,
split on whitespace with name.trim().split(/\s+/) or split(" ").filter(Boolean),
map to n[0] safely (fallback to ""), join, toUpperCase(), and slice(0,2) so
empty segments or leading/trailing spaces don't yield undefined initials.
In `@apps/marketing/src/app/blog/components/mdx-components.tsx`:
- Around line 11-32: The extractCodeFromChildren helper currently casts any
regex match to BundledLanguage which can lead to runtime errors in codeToHtml;
update extractCodeFromChildren to validate the extracted language against
Shiki's supported languages (or return "text") instead of unguarded casting, and
modify highlightCode (the function calling codeToHtml) to wrap codeToHtml calls
in try/catch and return a plaintext fallback on error; also ensure CodeBlock's
useEffect that calls highlightCode attaches a .catch handler or awaits inside a
try/catch so rejected promises are handled and the component falls back to plain
rendering.
♻️ Duplicate comments (3)
apps/marketing/src/lib/blog.ts (1)
49-51: Silent error swallowing in catch block.This was previously flagged. The empty catch block discards error information, making debugging difficult. Per coding guidelines, errors should at minimum be logged with context.
apps/marketing/src/app/blog/[slug]/components/BlogPostLayout/BlogPostLayout.tsx (1)
10-20: Unusedtocprop and duplicateTocIteminterface.This was previously flagged. The
TocIteminterface duplicates the one in@/lib/blog, and thetocprop is defined but never used in the component. Either implement the TOC feature or remove the dead code.apps/marketing/src/app/blog/[slug]/page.tsx (1)
12-28:tocis computed but unused downstream.The
tocis extracted and passed toBlogPostLayout, but as noted in the layout component review, it's not being rendered. This is dead code until the TOC feature is implemented.
🧹 Nitpick comments (8)
apps/marketing/content/blog/parallel-agents-guide.mdx (1)
65-73: Example code lacks error handling.The
fetchUserfunction doesn't handle potential failures fromfetch()orresponse.json(). While this is documentation, readers may copy this pattern. Consider adding basic error handling to demonstrate best practices:useEffect(() => { async function fetchUser() { try { const response = await fetch(`/api/users/${userId}`); if (!response.ok) throw new Error('Failed to fetch'); const data = await response.json(); setUser(data); } catch (error) { console.error('[UserProfile/fetchUser]', error); } finally { setLoading(false); } } fetchUser(); }, [userId]);apps/marketing/src/app/blog/components/AuthorAvatar/AuthorAvatar.tsx (1)
55-59: Hardcoded light-mode colors in tooltip.The tooltip uses explicit light-mode colors (
bg-white,text-black,border-gray-200). If the blog supports dark mode, this may look inconsistent. If intentional for contrast, consider adding a comment explaining the choice.apps/marketing/src/lib/blog.ts (1)
99-118: Slugify logic is duplicated across files.The ID generation logic here (lines 110-113) duplicates the
slugifyfunction inmdx-components.tsx. Consider extracting this to a shared utility to ensure consistent slug generation between TOC extraction and heading rendering.♻️ Suggested refactor
+export function slugify(text: string): string { + return text + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/(^-|-$)/g, ""); +} + export function extractToc(content: string): TocItem[] { const headingRegex = /^(#{2,3})\s+(.+)$/gm; const toc: TocItem[] = []; for (const match of content.matchAll(headingRegex)) { const hashes = match[1]; const heading = match[2]; if (!hashes || !heading) continue; const level = hashes.length; const text = heading.trim(); - const id = text - .toLowerCase() - .replace(/[^a-z0-9]+/g, "-") - .replace(/(^-|-$)/g, ""); + const id = slugify(text); toc.push({ id, text, level }); } return toc; }Then in
mdx-components.tsx, import and use the sharedslugifyfunction.apps/marketing/src/app/blog/components/mdx-components.tsx (1)
4-9: Duplicated slugify function.This
slugifyimplementation is identical to the ID generation logic inblog.tsextractToc. As noted in the other file, consider extracting to a shared utility.apps/marketing/src/app/blog/components/BlogCard/BlogCard.tsx (1)
36-44: Hardcoded author metadata limits extensibility.The
titleandtwitterHandleprops are hardcoded for all posts. If different authors contribute, this will display incorrect information.Consider extending
BlogPostto include optional author metadata or creating an author lookup configuration.♻️ Suggested approach
Option 1: Extend BlogPost interface:
// In blog.ts export interface BlogPost { // ... existing fields authorTitle?: string; authorTwitter?: string; }Option 2: Create author config lookup:
const AUTHORS: Record<string, { title: string; twitter: string }> = { "Avi Peltz": { title: "Cofounder, Superset", twitter: "avimakesrobots" }, // ... other authors };apps/marketing/src/app/blog/[slug]/components/BlogPostLayout/BlogPostLayout.tsx (2)
32-39: Extract magic number to named constant.The value
384(and383) appears in the background gradient calculation. Per coding guidelines, extract magic numbers to named constants for clarity.♻️ Suggested refactor
+"use client"; + +const CONTENT_HALF_WIDTH_PX = 384; + // In the style block: style={{ backgroundImage: ` - linear-gradient(to right, transparent 0%, transparent calc(50% - 384px), rgba(255,255,255,0.06) calc(50% - 384px), rgba(255,255,255,0.06) calc(50% - 383px), transparent calc(50% - 383px), transparent calc(50% + 383px), rgba(255,255,255,0.06) calc(50% + 383px), rgba(255,255,255,0.06) calc(50% + 384px), transparent calc(50% + 384px)) + linear-gradient(to right, transparent 0%, transparent calc(50% - ${CONTENT_HALF_WIDTH_PX}px), rgba(255,255,255,0.06) calc(50% - ${CONTENT_HALF_WIDTH_PX}px), rgba(255,255,255,0.06) calc(50% - ${CONTENT_HALF_WIDTH_PX - 1}px), transparent calc(50% - ${CONTENT_HALF_WIDTH_PX - 1}px), transparent calc(50% + ${CONTENT_HALF_WIDTH_PX - 1}px), rgba(255,255,255,0.06) calc(50% + ${CONTENT_HALF_WIDTH_PX - 1}px), rgba(255,255,255,0.06) calc(50% + ${CONTENT_HALF_WIDTH_PX}px), transparent calc(50% + ${CONTENT_HALF_WIDTH_PX}px)) `, }}
64-68: Hardcoded author metadata (same issue as BlogCard).The
titleandtwitterHandleare hardcoded here as well. This should be addressed together with theBlogCardcomponent.apps/marketing/src/app/blog/page.tsx (1)
18-25: Extract shared background gradient constant.The same
384pxmagic number and gradient pattern appears here and inBlogPostLayout. Consider extracting to a shared constant or utility.
| const initials = name | ||
| .split(" ") | ||
| .map((n) => n[0]) | ||
| .join("") | ||
| .toUpperCase() | ||
| .slice(0, 2); |
There was a problem hiding this comment.
Edge case: empty string segments in name splitting.
If name contains leading/trailing spaces or consecutive spaces (e.g., "John Doe" or " John"), split(" ") produces empty strings, and n[0] returns undefined, resulting in initials like "JU" (from undefined coerced).
Suggested fix
const initials = name
.split(" ")
+ .filter(Boolean)
.map((n) => n[0])
.join("")
.toUpperCase()
.slice(0, 2);🤖 Prompt for AI Agents
In `@apps/marketing/src/app/blog/components/AuthorAvatar/AuthorAvatar.tsx` around
lines 31 - 36, The initials computation can produce undefined when name has
extra spaces; update the initials logic (the initials constant in AuthorAvatar)
to normalize and filter segments before taking first chars — e.g., split on
whitespace with name.trim().split(/\s+/) or split(" ").filter(Boolean), map to
n[0] safely (fallback to ""), join, toUpperCase(), and slice(0,2) so empty
segments or leading/trailing spaces don't yield undefined initials.
| function extractCodeFromChildren(children: React.ReactNode): { | ||
| code: string; | ||
| language: BundledLanguage; | ||
| } { | ||
| if ( | ||
| children && | ||
| typeof children === "object" && | ||
| "props" in children && | ||
| children.props | ||
| ) { | ||
| const codeProps = children.props as { | ||
| children?: string; | ||
| className?: string; | ||
| }; | ||
| const code = codeProps.children?.trim() ?? ""; | ||
| const className = codeProps.className ?? ""; | ||
| const match = className.match(/language-(\w+)/); | ||
| const language = (match?.[1] ?? "text") as BundledLanguage; | ||
| return { code, language }; | ||
| } | ||
| return { code: String(children ?? ""), language: "text" as BundledLanguage }; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, locate the mdx-components.tsx file
find . -name "mdx-components.tsx" -type fRepository: superset-sh/superset
Length of output: 158
🏁 Script executed:
# Search for BundledLanguage type definition
rg "BundledLanguage" --type ts --type tsx -A 2 -B 2Repository: superset-sh/superset
Length of output: 91
🏁 Script executed:
# Find BlogCodeBlock component
rg "BlogCodeBlock" --type ts --type tsx -A 5 -B 2Repository: superset-sh/superset
Length of output: 91
🏁 Script executed:
# Check imports in mdx-components.tsx to understand dependencies
rg "import.*Shiki|from.*shiki" --type ts --type tsx -A 2 -B 2Repository: superset-sh/superset
Length of output: 91
🏁 Script executed:
# Read the mdx-components.tsx file directly
cat -n ./apps/marketing/src/app/blog/components/mdx-components.tsxRepository: superset-sh/superset
Length of output: 3748
🏁 Script executed:
# Search for BundledLanguage without type restriction
rg "BundledLanguage" -A 2 -B 2Repository: superset-sh/superset
Length of output: 3589
🏁 Script executed:
# Search for BlogCodeBlock without type restriction
rg "BlogCodeBlock" -A 5 -B 2Repository: superset-sh/superset
Length of output: 3127
🏁 Script executed:
# Search for Shiki imports
rg "from.*shiki" -A 2 -B 2Repository: superset-sh/superset
Length of output: 1295
🏁 Script executed:
# Read BlogCodeBlock.tsx fully
cat -n ./apps/marketing/src/app/blog/components/BlogCodeBlock/BlogCodeBlock.tsxRepository: superset-sh/superset
Length of output: 846
🏁 Script executed:
# Read CodeBlock.tsx to see how codeToHtml is called
cat -n ./packages/ui/src/components/ai-elements/code-block.tsx | head -80Repository: superset-sh/superset
Length of output: 2144
🏁 Script executed:
# Check for Shiki error handling or validation patterns in codebase
rg "codeToHtml" -A 10 -B 2Repository: superset-sh/superset
Length of output: 2346
🏁 Script executed:
# Read the rest of code-block.tsx
cat -n ./packages/ui/src/components/ai-elements/code-block.tsx | tail -50Repository: superset-sh/superset
Length of output: 1445
🏁 Script executed:
# Search for try-catch blocks around code highlighting
rg "try|catch|error|Error" ./packages/ui/src/components/ai-elements/code-block.tsx -A 2 -B 2Repository: superset-sh/superset
Length of output: 691
🏁 Script executed:
# Check how highlightCode is called/used
rg "highlightCode" -A 5 -B 2Repository: superset-sh/superset
Length of output: 1300
🌐 Web query:
Shiki BundledLanguage type definition and how it handles unknown language identifiers
💡 Result:
BundledLanguage type (Shiki)
In Shiki’s prebuilt bundles (e.g. shiki/bundle/web), BundledLanguage is a TypeScript string-literal union of the language IDs included in that bundle, and the bundle also exports bundledLanguages: Record<BundledLanguage, DynamicImportLanguageRegistration>. (app.unpkg.com)
So, for a given bundle, only those IDs are valid at the type level (e.g. 'js', 'ts', 'astro', etc. in the web bundle). (app.unpkg.com)
How Shiki handles unknown language identifiers (runtime)
- Shiki treats plain-text languages specially and bypasses highlighting when
langis missing or is one of:plaintext,txt,text,plain. (app.unpkg.com) - For a truly unknown / not-bundled language id (i.e. not loaded/registered), Shiki’s APIs are expected to throw an error (so callers commonly wrap highlighting in
try/catchand/or map unknown inputs tolang: 'text'). (jsr.io)
Citations:
- 1: https://app.unpkg.com/shiki%401.22.0/files/dist/bundle-web.d.mts?utm_source=openai
- 2: https://app.unpkg.com/shiki%401.22.0/files/dist/bundle-web.d.mts?utm_source=openai
- 3: https://app.unpkg.com/%40shikijs/core%401.16.1/files/dist/index.mjs?utm_source=openai
- 4: https://jsr.io/%40shikijs/shiki?utm_source=openai
🏁 Script executed:
# Get the full useEffect hook in CodeBlock
cat -n ./packages/ui/src/components/ai-elements/code-block.tsx | sed -n '84,120p'Repository: superset-sh/superset
Length of output: 1599
Add error handling for invalid language identifiers in code highlighting.
The as BundledLanguage cast on lines 28 and 31 bypasses TypeScript's type safety. The regex /language-(\w+)/ extracts any word sequence (e.g., language-foobar → foobar), which may not exist in Shiki's supported languages bundle. While codeToHtml accepts a BundledLanguage type at compile time, Shiki throws a runtime error for unsupported language identifiers.
The fallback to "text" only applies when the regex doesn't match—not when it matches an invalid language. The highlightCode function (code-block.tsx lines 61–72) has no error handling around codeToHtml calls, and CodeBlock's useEffect (line 88) calls highlightCode().then(...) without a .catch() handler, risking unhandled promise rejections.
Add validation against supported languages, or wrap codeToHtml calls in try-catch to default to plaintext rendering on failure.
🤖 Prompt for AI Agents
In `@apps/marketing/src/app/blog/components/mdx-components.tsx` around lines 11 -
32, The extractCodeFromChildren helper currently casts any regex match to
BundledLanguage which can lead to runtime errors in codeToHtml; update
extractCodeFromChildren to validate the extracted language against Shiki's
supported languages (or return "text") instead of unguarded casting, and modify
highlightCode (the function calling codeToHtml) to wrap codeToHtml calls in
try/catch and return a plaintext fallback on error; also ensure CodeBlock's
useEffect that calls highlightCode attaches a .catch handler or awaits inside a
try/catch so rejected promises are handled and the component falls back to plain
rendering.
# Conflicts: # apps/marketing/src/app/globals.css
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In
`@apps/marketing/src/app/blog/`[slug]/components/BlogPostLayout/BlogPostLayout.tsx:
- Around line 54-58: The AuthorAvatar is receiving hardcoded title and
twitterHandle while name uses post.author, causing mismatched metadata; update
the data model and JSX to pass real author metadata: add authorTitle and
authorTwitter fields to the BlogPost frontmatter/interface (and to whatever
parser builds post), populate them in existing posts or provide sensible
fallbacks, then replace the hardcoded strings in the BlogPostLayout component by
passing post.authorTitle and post.authorTwitter into AuthorAvatar (or, if you
prefer a fixed set, implement an author lookup map keyed by post.author and use
that to supply title and twitterHandle; alternatively remove those props if no
reliable source exists).
♻️ Duplicate comments (2)
apps/marketing/src/lib/blog.ts (1)
43-45: Silent error swallowing in catch block.The empty catch block discards error information, making debugging difficult. Per coding guidelines, errors should at minimum be logged with context.
Proposed fix
- } catch { + } catch (error) { + console.error(`[blog/parseFrontmatter] Failed to parse ${filePath}:`, error); return null; }apps/marketing/src/app/blog/[slug]/components/BlogPostLayout/BlogPostLayout.tsx (1)
10-16: Unusedtocprop still present in interface.The
tocprop is defined inBlogPostLayoutProps(line 12) but is never destructured or used in the component. Either implement the table of contents feature or remove this dead code.If TOC is not needed, remove the unused prop
-import { type BlogPost, formatBlogDate, type TocItem } from "@/lib/blog-utils"; +import { type BlogPost, formatBlogDate } from "@/lib/blog-utils"; interface BlogPostLayoutProps { post: BlogPost; - toc: TocItem[]; children: ReactNode; }
| <AuthorAvatar | ||
| name={post.author} | ||
| title="Cofounder, Superset" | ||
| twitterHandle="avimakesrobots" | ||
| /> |
There was a problem hiding this comment.
Hardcoded author metadata will be incorrect for different authors.
The title and twitterHandle props are hardcoded to "Cofounder, Superset" and "avimakesrobots" respectively, but name uses post.author. If blog posts have different authors, this will display incorrect metadata.
Consider either:
- Adding
authorTitleandauthorTwitterfields to theBlogPostinterface and frontmatter - Creating an author lookup map if there's a fixed set of authors
- Removing the author details if they can't be reliably sourced
🤖 Prompt for AI Agents
In
`@apps/marketing/src/app/blog/`[slug]/components/BlogPostLayout/BlogPostLayout.tsx
around lines 54 - 58, The AuthorAvatar is receiving hardcoded title and
twitterHandle while name uses post.author, causing mismatched metadata; update
the data model and JSX to pass real author metadata: add authorTitle and
authorTwitter fields to the BlogPost frontmatter/interface (and to whatever
parser builds post), populate them in existing posts or provide sensible
fallbacks, then replace the hardcoded strings in the BlogPostLayout component by
passing post.authorTitle and post.authorTwitter into AuthorAvatar (or, if you
prefer a fixed set, implement an author lookup map keyed by post.author and use
that to supply title and twitterHandle; alternatively remove those props if no
reliable source exists).
Summary
Features
/blog) - Grid background, category tags in mono font, hard-bordered cards/blog/[slug]) - Centered hero header, "← Blog" back link with line dividergenerateStaticParamsfor SSGTest plan
/blogand verify listing page renders with grid backgroundSummary by CodeRabbit
New Features
Chores
✏️ Tip: You can customize this high-level summary in your review settings.