Blog Components
Small presentational components that make up the blog index and post pages.
Where posts come from
All blog posts are defined in src/blog/_assets/content.ts as entries in the articles array. Each entry is a plain TypeScript object — no MDX, no CMS, no database required. The content field is an HTML string styled automatically by @tailwindcss/typography.
// src/blog/_assets/content.ts
{
slug: "my-first-post",
title: "My First Post",
description: "A short summary shown on the card.",
date: "2026-06-01",
cover: "/blog/cover.png",
author: {
name: "Your Name",
role: "Founder",
avatar: "/portrait.jpg",
},
tags: ["launch", "saas"],
content: `<p>Your post content as HTML.</p>`,
}slug— used as the URL:/blog/my-first-post. Must be URL-safe (lowercase, hyphens, no spaces).title— displayed as the card heading and page title.description— the excerpt shown on the card and used as the meta description.date— ISO 8601 date string. Controls display order (newest first) and the formatted date inPostMeta.cover— optional image path relative to/public. Falls back to a gradient placeholder if omitted.author.name— shown inPostMeta. Also used as the avatar fallback initial.author.role— optional role/title shown alongside the name.author.avatar— optional image URL. If omitted, a circle with the author's first initial is shown.tags— array of strings rendered asCategoryTagpills on the card.content— raw HTML for the post body. Use<p>, <h2>, <ul>, <li>, <strong>, <a>, <code>etc. Tailwind Typography handles all the styling.
CardArticle
Renders a blog post preview card with a 16:9 cover image (or gradient fallback), the PostMeta row, the post title as a link, the description excerpt, and CategoryTag pills for each tag. Used on the /blog index page.
import CardArticle from "@/components/blog/CardArticle";
<CardArticle post={post} />The only prop is post — a BlogPost object with the same shape as the entries in content.ts. The card resolves the cover image, author, tags, and link internally.
CategoryTag
A small rounded pill that displays a tag name. Non-interactive — it's a presentational <span>. Rendered inside CardArticle for each string in post.tags.
import CategoryTag from "@/components/blog/CategoryTag";
<CategoryTag name="launch" />PostMeta
Renders the author avatar, author name, and formatted publish date in a single row. Uses Avatar internally. Shown at the top of both the card and the full post page.
import PostMeta from "@/components/blog/PostMeta";
<PostMeta post={post} />Avatar
A circular avatar. If author.avatar is set it renders the image; otherwise it shows a circle with the first letter of author.name as a fallback. Used inside PostMeta — you rarely need to use it directly.
import Avatar from "@/components/blog/Avatar";
<Avatar author={{ name: "Jane", avatar: "/jane.jpg" }} size={36} />author— an object withname(required) and optionalavatar(image URL).size— pixel size for both width and height. Defaults to36.
articles array in src/blog/_assets/content.ts. No routes, no files, no CMS — the slug becomes the URL automatically via the /blog/[slug] dynamic route./public/blog/ and reference them as "/blog/my-cover.png". Keep them at 1200×675 px (16:9) for the best appearance on the card and when shared on social media.