We needed two blog sections: one for product updates and one for engineering deep-dives. Same rendering pipeline, different content directories, different color themes, different comment policies. The naive approach — duplicating the entire blog infrastructure — would have worked but violated every instinct I have about code reuse.
So we built a factory.
The Problem
The Lumo blog (/blog) was working well: MDX files, static generation, pagination, tag filtering, RSS feed. Then we decided we wanted an engineering section (/engineering) with:
- Its own content directory (
content/engineering/) - An emerald green theme instead of blue
- Comments enabled (the engineering audience has substantive feedback)
- Its own RSS feed at
/engineering/feed.xml - Independent pagination
Copying the blog code would have given us two independent systems that drift apart over time. Every future improvement would need to be applied twice. That's not engineering — that's job security through artificial complexity.
The Factory Pattern
The solution was a createPostsLib factory that takes a content directory and returns a complete posts API:
// lib/blog/posts.ts
export function createPostsLib(contentDirectory: string) {
function getAllPosts() {
// Read MDX files from the content directory
// Parse frontmatter, sort by date, filter drafts
}
function getPostBySlug(slug: string) {
// Read and compile a single post
}
function getPageCount() {
// Calculate pagination based on total posts
}
return { getAllPosts, getPostBySlug, getPageCount };
}
// Default blog instance
export const {
getAllPosts,
getPostBySlug,
getPageCount,
} = createPostsLib("content/blog");The engineering section gets its own instance:
// lib/engineering/posts.ts
export const {
getAllPosts: getEngineeringPosts,
getPostBySlug: getEngineeringPostBySlug,
getPageCount: getEngineeringPageCount,
} = createPostsLib("content/engineering");Same parsing logic, same MDX compilation, same frontmatter schema. Different directory, different export names. Zero duplication.
Theme Variants
The shared components needed to support different visual themes without becoming a mess of conditional styles. We added a variant prop:
// BlogGrid accepts a variant for color theming
<BlogGrid
posts={posts}
basePath="/engineering"
variant="emerald"
sectionLabel="Engineering"
/>
// BlogPagination respects basePath for URL generation
<BlogPagination
currentPage={page}
totalPages={totalPages}
basePath="/engineering"
/>The variant prop controls accent colors (blue vs emerald), badge styles, and hover states. The basePath prop ensures pagination URLs and post links point to the correct section. Two props, full customization, no duplication.
Comment Policy Per Section
BlogPostEngagement got a showComments prop that defaults to false:
<BlogPostEngagement
postSlug={slug}
showComments={true} // Engineering posts get comments
/>Blog posts show emoji reactions only. Engineering posts show the full threaded comment system. The decision was deliberate: engineering readers are more likely to have technical questions and corrections worth preserving in a thread. Blog readers just want to signal "I found this useful" and move on.
The Result
Adding a third blog section (say, a design blog or a changelog blog) would take about 15 minutes:
- Create
content/design/directory - Create
lib/design/posts.tswithcreatePostsLib("content/design") - Create
app/design/page.tsxwith a new color variant - Done
No new components, no new rendering logic, no new RSS generation code. Just a new instance of the same factory pointed at a new directory.
The best abstractions are the ones that make the next feature boring to implement.
Abdul Rafay Founder, Syntax Lab Technology