Homepage Change Log 03: Recent Sections and the Content Flow Behind Them
The second screen no longer behaves like a set of feature cards. Recent notes and recent photography expose real content, while Velite and YAML keep the data flow clean.
The first screen answers whose site this is. The second screen needs to answer why a visitor should keep scrolling.
I did not want the second screen to become a grid of big entry cards. That would feel too much like a product landing page. The better fit was to expose real content: recent notes and recent photography.
Recent Notes Work Better as a Timeline
Blog posts are time-based. A timeline expresses that better than a card grid. It lets visitors see what changed recently, and it makes the site feel alive.
The data comes from Velite:
const recentPosts = sortByDateDesc(
blogs.filter((post) => post.lang === locale && !post.draft),
).slice(0, 4);This does three things:
- It keeps only posts in the active language.
- It excludes drafts.
- It sorts by date and takes the latest four posts.
The page does not read files or parse MDX. Velite turns content into structured data before the page renders.
Timeline Alignment Is a Small Trap
The timeline looked simple, but the details mattered. Earlier versions had dots covering dates, and the dashed line did not sit on the dot center. The stable version separates the marker, date, and title into grid columns:
<Link className="grid grid-cols-[1.5rem_1fr] sm:grid-cols-[1.5rem_8rem_1fr]">
<span aria-hidden="true">{/* dot and dashed line */}</span>
<time>{formatDate(post.date, locale)}</time>
<span>{post.title}</span>
</Link>The marker column handles the dot and the dashed line. The date has its own column. The title has its own column. Once those responsibilities are separated, the line no longer reacts to text length.
Recent Photography Should Not Crop Everything
The photography section started as a single changing image, then became a wall of recent photos. The main rule was to keep the original aspect ratio whenever possible. A small image can still respect the photograph.
The current version uses CSS columns:
<div className="columns-2 gap-3 sm:columns-3 lg:columns-4">
{latestPhotos.map((photo) => (
<Image
src={photo.thumbnail ?? photo.image}
width={photo.width ?? 1200}
height={photo.height ?? 1600}
className="h-auto w-full"
/>
))}
</div>h-auto w-full keeps the image ratio. CSS columns do the light masonry work. It is not as controlled as a full layout algorithm, but it is enough for a recent-photo preview.
The Homepage Does Not Own Image Processing
The homepage reads generated YAML from content/gallery. It does not know how images were processed or uploaded:
title: "Brooklyn Botanic Garden"
image: "https://img.huzejun.com/..."
thumbnail: "https://img.huzejun.com/..."
width: 1200
height: 1600
blurDataURL: "data:image/webp;base64,..."That keeps the page simple. Processing, uploading, and YAML generation stay in scripts. Rendering stays in the page.
Takeaway
Recent notes and recent photography make the homepage more than a set of entrances. They turn it into a window into the archive.
Posts come from MDX, photos come from YAML, and music comes from Last.fm. The homepage arranges those sources into one experience without mixing their responsibilities. That boundary will matter when more sections are added later.

