Skip to content
Consultation

Related Articles Without a Waterfall

Adityo Guni Waluyo

What commit 70a43a9 taught me about related articles, parallel fetching, a CSS timeline, and a file-tree TOC in a Next.js blog.

I pushed commit 70a43a9 and immediately opened a blog post on the local development server to check the render. The sidebar next to the article was still blank. For a Next.js App Router site that's been live for months, having zero cross-links on a detail page always bugged me — readers finish one post and just bounce.

My first dumb instinct was to fetch all posts in the client component, filter by tags in the browser, and slice the array. Easy, right? No. That meant shipping the entire post list to the client just to show a few links. I killed that idea in the draft stage.

I decided the server component should do the matching. It takes the first tag only. If a post has nextjs, react, tailwind, I only care about nextjs for relations. This keeps the query narrow and the results actually relevant instead of a messy grab-bag of loosely tied topics. I capped the output at 8 items. Anything more just bloats the sidebar and nobody scrolls that far anyway.

First-tag match and the 8-item cap

The logic lives right in the detail page server component. I pass the main article's tags, take firstTag, and query the content source. If it returns 20 matches, I slice the first 8. Simple and predictable.

I used to think related-articles needed a fancy recommendation engine with weights. It doesn't. For a personal blog, "other things with the same primary topic" is enough. I personally prefer this over a scored system because when a reader asks why a link is there, I can point to the tag without sounding like a black box.

Parallel fetch and the timeline UI

The data layer wasn't slow, but why wait? The main article query and the related query don't depend on each other's output — I already have the slug from the route param and can derive the tag synchronously from the cached article meta before the fetch even starts.

Here's the part where I almost shot myself in the foot. I originally wrote the fetch as a waterfall:

The related query only starts after the main article finishes. Since both hit the same data layer, I moved them into a single Promise.all call.

const [article, related] = await Promise.all([
  getArticle(slug, locale),
  getRelatedArticles(firstTag, locale),
]);

Per MDN [1], Promise.all resolves when every promise in the array finishes. But the failure semantics are strict: if getRelated rejects, the whole Promise.all rejects. Next.js docs [2] note that in the App Router, an uncaught rejection in a server component bubbles to the nearest error boundary. I didn't want a sidebar glitch to white-screen the entire post. So I wrapped the related fetch in its own try/catch inside the array, returning an empty related list on failure. That way Promise.all still resolves with the main article and an empty related list, so the page renders fine.

For the date labels in the timeline, I dropped manual string slicing. Intl.DateTimeFormat [3] handles locale rules cleanly. I call new Intl.DateTimeFormat('en-US', { month: 'short', year: 'numeric' }).format(date) and it just works across entries without me tracking padding or locales.

The UI itself is a vertical timeline. Each related post is a node on the line. I used Tailwind's line-clamp [4] on the title to keep things to two lines max. No custom CSS, no JS measurement, just line-clamp-2 and the overflow hides itself. Previously I tried truncate but that killed the title at one line and looked bald. Two lines keeps the context of the post topic visible.

The same commit also touched the BlogToc component. It now renders like a file-tree: H2 elements become folders, H3 elements become guide lines nested under them. Before this, the Toc was just a flat list of anchors. Turning H2 into folders means a reader scanning a 10-minute guide can see the chapter boundaries. The H3 guide lines sit indented, showing the exact steps without screaming for attention. It's a small visual touch but makes long technical guides feel like a structured document instead of a scroll of headers.

I'm keeping the timeline strict on the 8-item limit. If I ever need more, I'll rethink the first-tag rule, but for now the sidebar finally does its job.

Sumber yang saya pakai

[1] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

[2] https://nextjs.org/docs/app/building-your-application/data-fetching/patterns

[3] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat

[4] https://tailwindcss.com/docs/line-clamp

Related articles