feat(marketing): add author registry and dynamic author metadata to blog#1150
Conversation
📝 WalkthroughWalkthroughAuthor handling was refactored: post frontmatter now uses author identifiers; new person MDX files and a people module provide structured Person objects; blog utilities, components, JSON‑LD, feed, and pages were updated to consume the Person shape instead of plain author strings. Changes
Sequence DiagramsequenceDiagram
participant Content as Content Files\n(blog .mdx)
participant BlogUtil as Blog Utilities\n(blog.ts / blog-utils.ts)
participant People as People Module\n(people.ts)
participant Component as UI Components\n(BlogCard / Layout / AuthorAvatar)
participant JsonLd as JSON‑LD Builder\n(JsonLd.tsx)
Content->>BlogUtil: parse frontmatter (author id e.g., "avi")
BlogUtil->>People: getPersonById("avi")
People-->>BlogUtil: Person { id, name, role, avatar, socials, content }
BlogUtil-->>Component: resolved BlogPost with author: Person
Component->>Component: render author.name, avatar, role, social links
Component->>JsonLd: provide author object (name, url?, sameAs?)
JsonLd->>JsonLd: build Article JSON‑LD with author object
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
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
🧪 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 |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@apps/marketing/src/lib/people.ts`:
- Around line 25-45: The console error in getPersonById currently uses prefix
"[people]" which omits the operation; update the logging call inside the catch
block to use the required "[domain/operation]" format (e.g.,
"[people/getPersonById]") and keep the existing message and error variable so it
logs like "[people/getPersonById] Failed to parse ${id}.mdx:" followed by the
error to conform to the pattern.
🧹 Nitpick comments (1)
apps/marketing/src/lib/blog.ts (1)
34-47: Defensively normalize non-string frontmatter author values.Frontmatter is untyped; if
authorisn’t a string, the fallback can render[object Object]and attempt invalid file lookups. A small guard keeps this predictable.🛡️ Suggested guard
- const authorId: string = data.author ?? "unknown"; + const authorId = + typeof data.author === "string" && data.author.trim().length > 0 + ? data.author + : "unknown";
| export function getPersonById(id: string): Person | null { | ||
| const filePath = path.join(peopleDirectory, `${id}.mdx`); | ||
|
|
||
| if (!fs.existsSync(filePath)) { | ||
| return null; | ||
| } | ||
|
|
||
| try { | ||
| const fileContent = fs.readFileSync(filePath, "utf-8"); | ||
| const { data, content } = matter(fileContent); | ||
| const validatedData = personSchema.parse(data); | ||
|
|
||
| return { | ||
| ...validatedData, | ||
| id, | ||
| content, | ||
| }; | ||
| } catch (error) { | ||
| console.error(`[people] Failed to parse ${id}.mdx:`, error); | ||
| return null; | ||
| } |
There was a problem hiding this comment.
Adjust the console prefix to the required [domain/operation] format.
Current prefix omits the operation segment.
🔧 Suggested fix
- console.error(`[people] Failed to parse ${id}.mdx:`, error);
+ console.error(`[people/getPersonById] Failed to parse ${id}.mdx:`, error);As per coding guidelines: Use prefixed console logging with pattern [domain/operation] message for all logging.
🤖 Prompt for AI Agents
In `@apps/marketing/src/lib/people.ts` around lines 25 - 45, The console error in
getPersonById currently uses prefix "[people]" which omits the operation; update
the logging call inside the catch block to use the required "[domain/operation]"
format (e.g., "[people/getPersonById]") and keep the existing message and error
variable so it logs like "[people/getPersonById] Failed to parse ${id}.mdx:"
followed by the error to conform to the pattern.
Replace hardcoded author data with a centralized author registry that resolves author IDs from MDX frontmatter to full metadata (name, title, twitter, github). Enriches JSON-LD with author URL and sameAs profiles for better SEO/E-E-A-T signals.
Replace hardcoded AUTHORS record with MDX content files in content/people/ (one file per author, parsed with gray-matter + Zod). Resolve author to full Person object during blog parsing so components access post.author.name/role/twitter directly instead of doing lookups.
- Add satya.mdx person file, update avi/kiet with avatars, LinkedIn, and correct socials - Add team avatar photos (avi.jpg, kiet.jpg, satya.webp) to public/team/ - Replace AuthorAvatar tooltip hover card with real avatar images using next/image - Show author name, role, date, and social icon links (X, GitHub, LinkedIn) in blog post header - Use react-icons (RiTwitterXFill, RiGithubFill, RiLinkedinBoxFill) for social icons - BlogCard now shows real author avatars in listing
6de8638 to
4b4115f
Compare
🧹 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: 1
🤖 Fix all issues with AI agents
In
`@apps/marketing/src/app/blog/`[slug]/components/BlogPostLayout/BlogPostLayout.tsx:
- Around line 75-103: In BlogPostLayout, the social icon-only links (anchors
rendered when author.twitter, author.github, author.linkedin are present) lack
accessible labels; update each <a> that wraps RiTwitterXFill, RiGithubFill, and
RiLinkedinBoxFill to include an appropriate aria-label (for example "Author on
X", "Author on GitHub", "Author on LinkedIn") or add visually-hidden text inside
the link, so screen readers get descriptive text while preserving existing
classes, target, and rel attributes.
🧹 Nitpick comments (2)
apps/marketing/src/app/blog/[slug]/components/BlogPostLayout/BlogPostLayout.tsx (1)
66-71: Hide the role separator when role is empty.Fallback authors with an empty role render a leading separator. Consider conditional rendering.
💡 Suggested tweak
<span className="text-xs text-muted-foreground"> - {author.role} - <span className="text-muted-foreground/50"> · </span> + {author.role && ( + <> + {author.role} + <span className="text-muted-foreground/50"> · </span> + </> + )} <time dateTime={post.date}>{formattedDate}</time> </span>apps/marketing/src/app/blog/[slug]/page.tsx (1)
36-56: Populate JSON‑LD author.url from any available profile.Right now
urlonly uses Twitter, so authors with GitHub/LinkedIn only won’t get a primary URL. Consider choosing the first available profile.💡 Suggested adjustment
const sameAs: string[] = []; if (author.twitter) { sameAs.push(`https://x.com/${author.twitter}`); } if (author.github) { sameAs.push(`https://github.com/${author.github}`); } if (author.linkedin) { sameAs.push(`https://linkedin.com/in/${author.linkedin}`); } + + const authorUrl = + author.twitter + ? `https://x.com/${author.twitter}` + : author.github + ? `https://github.com/${author.github}` + : author.linkedin + ? `https://linkedin.com/in/${author.linkedin}` + : undefined; <ArticleJsonLd title={post.title} description={post.description} author={{ name: author.name, - url: author.twitter ? `https://x.com/${author.twitter}` : undefined, + url: authorUrl, sameAs: sameAs.length > 0 ? sameAs : undefined, }}
| {author.twitter && ( | ||
| <a | ||
| href={`https://x.com/${author.twitter}`} | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| className="text-muted-foreground hover:text-foreground transition-colors" | ||
| > | ||
| <RiTwitterXFill className="size-4" /> | ||
| </a> | ||
| )} | ||
| {author.github && ( | ||
| <a | ||
| href={`https://github.com/${author.github}`} | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| className="text-muted-foreground hover:text-foreground transition-colors" | ||
| > | ||
| <RiGithubFill className="size-4" /> | ||
| </a> | ||
| )} | ||
| {author.linkedin && ( | ||
| <a | ||
| href={`https://linkedin.com/in/${author.linkedin}`} | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| className="text-muted-foreground hover:text-foreground transition-colors" | ||
| > | ||
| <RiLinkedinBoxFill className="size-4" /> | ||
| </a> |
There was a problem hiding this comment.
Add accessible labels to icon-only social links.
Screen readers won’t have descriptive text for these links. Add aria-label (or visually hidden text) per link.
🔧 Suggested fix
{author.twitter && (
<a
href={`https://x.com/${author.twitter}`}
+ aria-label={`${author.name} on X`}
target="_blank"
rel="noopener noreferrer"
className="text-muted-foreground hover:text-foreground transition-colors"
>
<RiTwitterXFill className="size-4" />
</a>
)}
{author.github && (
<a
href={`https://github.com/${author.github}`}
+ aria-label={`${author.name} on GitHub`}
target="_blank"
rel="noopener noreferrer"
className="text-muted-foreground hover:text-foreground transition-colors"
>
<RiGithubFill className="size-4" />
</a>
)}
{author.linkedin && (
<a
href={`https://linkedin.com/in/${author.linkedin}`}
+ aria-label={`${author.name} on LinkedIn`}
target="_blank"
rel="noopener noreferrer"
className="text-muted-foreground hover:text-foreground transition-colors"
>
<RiLinkedinBoxFill className="size-4" />
</a>
)}📝 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.
| {author.twitter && ( | |
| <a | |
| href={`https://x.com/${author.twitter}`} | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| className="text-muted-foreground hover:text-foreground transition-colors" | |
| > | |
| <RiTwitterXFill className="size-4" /> | |
| </a> | |
| )} | |
| {author.github && ( | |
| <a | |
| href={`https://github.com/${author.github}`} | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| className="text-muted-foreground hover:text-foreground transition-colors" | |
| > | |
| <RiGithubFill className="size-4" /> | |
| </a> | |
| )} | |
| {author.linkedin && ( | |
| <a | |
| href={`https://linkedin.com/in/${author.linkedin}`} | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| className="text-muted-foreground hover:text-foreground transition-colors" | |
| > | |
| <RiLinkedinBoxFill className="size-4" /> | |
| </a> | |
| {author.twitter && ( | |
| <a | |
| href={`https://x.com/${author.twitter}`} | |
| aria-label={`${author.name} on X`} | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| className="text-muted-foreground hover:text-foreground transition-colors" | |
| > | |
| <RiTwitterXFill className="size-4" /> | |
| </a> | |
| )} | |
| {author.github && ( | |
| <a | |
| href={`https://github.com/${author.github}`} | |
| aria-label={`${author.name} on GitHub`} | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| className="text-muted-foreground hover:text-foreground transition-colors" | |
| > | |
| <RiGithubFill className="size-4" /> | |
| </a> | |
| )} | |
| {author.linkedin && ( | |
| <a | |
| href={`https://linkedin.com/in/${author.linkedin}`} | |
| aria-label={`${author.name} on LinkedIn`} | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| className="text-muted-foreground hover:text-foreground transition-colors" | |
| > | |
| <RiLinkedinBoxFill className="size-4" /> | |
| </a> | |
| )} |
🤖 Prompt for AI Agents
In
`@apps/marketing/src/app/blog/`[slug]/components/BlogPostLayout/BlogPostLayout.tsx
around lines 75 - 103, In BlogPostLayout, the social icon-only links (anchors
rendered when author.twitter, author.github, author.linkedin are present) lack
accessible labels; update each <a> that wraps RiTwitterXFill, RiGithubFill, and
RiLinkedinBoxFill to include an appropriate aria-label (for example "Author on
X", "Author on GitHub", "Author on LinkedIn") or add visually-hidden text inside
the link, so screen readers get descriptive text while preserving existing
classes, target, and rel attributes.
Summary
apps/marketing/src/lib/authors.ts) mapping author IDs to full metadata (name, title, twitter, github)avi,kiet) instead of inconsistent full namesurlandsameAssocial profiles for improved SEO/E-E-A-T signalsTest plan
bun run typecheck --filter=@superset/marketingpassesbun run lintpassesbun test— 1193 tests pass, 0 failuresSummary by CodeRabbit
New Features
Refactor