Vibe Code to codebykarun.com: The Content Pipeline (Part 4)

5 min read
mdxnextjsperformancevibe coding

Part 4 of 5 • Previously: First Hour of Development

In Part 3, we built the foundation. Now let's make it production-ready with proper caching, optimization, and all the details that matter.

The Initial Implementation

We had basic file loading, but it wasn't optimized. Here's how we refined it:

Initial Prompt:

I need to load MDX files from the content directory, parse frontmatter 
metadata, and make the content available to my Next.js pages. The solution 
should work with Server Components, use async/await for file operations, 
and sort articles by date (newest first). Include proper TypeScript types.

Iteration Prompt:

Can you add error handling for missing files and invalid frontmatter? 
Also, I want to fetch a single article by slug, not just all articles.

This gave us robust file loading with proper error handling.

Adding Performance: Caching

The next issue was performance:

Caching Prompt:

These file reads will happen on every request in development. How can I add 
caching to improve performance? I'm using Next.js 15's App Router.

The Response: "Next.js 15 caches fetch requests by default, but filesystem operations need manual caching. Let's use unstable_cache..."

import { unstable_cache } from 'next/cache';

export const getArticles = unstable_cache(
  async (category: 'coding' | 'personal'): Promise<Article[]> => {
    const contentDir = path.join(process.cwd(), 'content', category);
    const files = await fs.readdir(contentDir);
    
    const articles = await Promise.all(
      files
        .filter(file => file.endsWith('.mdx'))
        .map(async (file) => {
          const filePath = path.join(contentDir, file);
          const fileContent = await fs.readFile(filePath, 'utf8');
          const { data, content } = matter(fileContent);
          
          return {
            slug: file.replace('.mdx', ''),
            title: data.title,
            excerpt: data.excerpt,
            date: data.date,
            tags: data.tags || [],
            content,
          };
        })
    );
    
    return articles.sort((a, b) => 
      new Date(b.date).getTime() - new Date(a.date).getTime()
    );
  },
  ['articles'],
  { revalidate: 3600 } // Cache for 1 hour
);

Result: Instant page loads in development and production.

Building the ArticleLayout Component

Now for the reading experience:

Initial Prompt:

Create an ArticleLayout component that wraps article content. It should 
display the title, publication date, estimated reading time, and tags. 
Use semantic HTML and make it look professional with Tailwind CSS.

This gave us a basic layout. But I wanted more:

Refinement Prompt:

This works but feels plain. I want better typography with proper hierarchy, 
metadata display with icons or separators, and that 'premium blog' feel you 
see on sites like Medium. The reading time should be calculated automatically 
based on word count (assume 200 words per minute). Use Tailwind's prose 
plugin for the article content.

Final Result:

import { formatDate } from '@/app/lib/utils';
import { Article } from '../types/article';

interface ArticleLayoutProps {
  article: Article;
  children: React.ReactNode;
}

function calculateReadTime(content: string): number {
  const wordsPerMinute = 200;
  const wordCount = content.trim().split(/\s+/).length;
  return Math.ceil(wordCount / wordsPerMinute);
}

export function ArticleLayout({ article, children }: ArticleLayoutProps) {
  const readTime = calculateReadTime(article.content);
  
  return (
    <article className="mx-auto max-w-3xl">
      <header className="mb-8 space-y-4">
        <h1 className="text-4xl font-bold tracking-tight lg:text-5xl">
          {article.title}
        </h1>
        
        <div className="flex flex-wrap items-center gap-4 text-sm text-muted-foreground">
          <time dateTime={article.date}>
            {formatDate(article.date)}
          </time>
          <span></span>
          <span>{readTime} min read</span>
        </div>
        
        {article.tags.length > 0 && (
          <div className="flex flex-wrap gap-2">
            {article.tags.map(tag => (
              <span
                key={tag}
                className="rounded-full bg-muted px-3 py-1 text-xs font-medium"
              >
                {tag}
              </span>
            ))}
          </div>
        )}
      </header>
      
      <div className="prose prose-neutral dark:prose-invert lg:prose-lg">
        {children}
      </div>
    </article>
  );
}

Dynamic Routes with SEO

Each article needs its own URL with proper SEO:

My Prompt:

I need to create dynamic route pages for individual articles. Each article 
should have its own URL based on its slug (e.g., /coding/my-article-slug). 
Use Next.js 15's App Router with generateStaticParams for static generation. 
Include proper SEO metadata generation from the article frontmatter, and 
return a 404 page if the article doesn't exist.

The Implementation:

// app/coding/[slug]/page.tsx
import { getArticles, getArticleBySlug } from '@/app/lib/articles';
import { ArticleLayout } from '@/app/components/ArticleLayout';
import { MDXContent } from '@/app/lib/mdx';
import { notFound } from 'next/navigation';

export async function generateStaticParams() {
  const articles = await getArticles('coding');
  return articles.map(article => ({
    slug: article.slug,
  }));
}

export async function generateMetadata({ params }: { params: { slug: string } }) {
  const article = await getArticleBySlug('coding', params.slug);
  
  if (!article) return {};
  
  return {
    title: article.title,
    description: article.excerpt,
  };
}

export default async function ArticlePage({ params }: { params: { slug: string } }) {
  const article = await getArticleBySlug('coding', params.slug);
  
  if (!article) notFound();
  
  return (
    <ArticleLayout article={article}>
      <MDXContent source={article.content} />
    </ArticleLayout>
  );
}

What This Got Right:

  1. Static generation - Fast, SEO-friendly
  2. Proper 404 handling - Clean error states
  3. SEO metadata - Automatic from frontmatter
  4. Type safety - No runtime surprises

The Iterative Process

Notice the pattern:

  1. Start with basic implementation
  2. Identify what's missing
  3. Ask specific questions
  4. Refine through conversation
  5. Test and validate

This is vibe coding at its best: iterative refinement through dialogue.

What I Learned

Building this pipeline taught me:

  • Next.js 15's caching strategies
  • How Server Components optimize performance
  • Reading time calculations
  • SEO metadata generation
  • Error handling patterns

I didn't just get code—I leveled up as a developer.

Coming Up Next

In Part 5, we'll tackle the design system and theming—how to create a beautiful, consistent look without being a designer.


Next in the Series

👉 Continue to Part 5: Design System & Theming

Learn how I created a professional design with dark mode support using AI guidance.


More in this series: Part 1: What is Vibe Coding?Part 2: Tech StackPart 3: First HourPart 5: Design SystemPart 6: Lessons LearnedPart 7: Best Practices