Vibe Code to codebykarun.com: The First 60 Minutes (Part 3)

4 min read
aiproductivitynextjsvibe coding

Part 3 of 5 • Previously: Choosing the Tech Stack

In Part 2, we chose our tech stack through informed conversation. Now comes the exciting part: actually building something.

Project Initialization

My Setup Prompt:

Let's initialize a new Next.js 15 project with TypeScript. I want to use 
Tailwind CSS for styling. Set up the project with a clean directory structure 
that separates content, components, utilities, and types. Use path aliases 
(@/app, @/components, etc.) to make imports cleaner. Also, I prefer Biome 
over ESLint for linting—it's faster.

Within minutes, I had:

  • Fresh Next.js 15 project
  • TypeScript configured with strict mode
  • Tailwind CSS installed and configured
  • Biome set up for linting
  • Path aliases in tsconfig.json

Building the Directory Structure

The AI suggested this structure, and we refined it together:

codebykarun-site/
├── app/
│   ├── components/
│   ├── lib/
│   ├── types/
│   ├── coding/
│   ├── personal/
│   └── layout.tsx
├── content/
│   ├── coding/
│   └── personal/
└── public/

Why this structure?

  • app/components - Reusable UI components
  • app/lib - Utility functions and helpers
  • app/types - TypeScript type definitions
  • app/coding & app/personal - Route handlers for each section
  • content/ - MDX blog posts separated by category

The Content Architecture

Content Architecture Prompt:

I need utility functions to read MDX files from the content directory, parse 
frontmatter metadata (title, excerpt, date, tags), and return typed Article 
objects. The functions should support two categories: 'coding' and 'personal'. 
Make sure everything works with Next.js 15's Server Components and includes 
proper error handling.

This gave us the foundation for the entire content system:

// app/types/article.ts
export interface Article {
  slug: string;
  title: string;
  excerpt: string;
  date: string;
  tags: string[];
  content: string;
}

// app/lib/articles.ts
import fs from 'fs/promises';
import path from 'path';
import matter from 'gray-matter';
import { Article } from '@/app/types/article';

export async function getArticles(
  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()
  );
}

The Magic of Contextual Problem Solving

My first attempt at MDX rendering hit a snag:

Me: "The MDX content isn't styling properly. Code blocks look broken."

Instead of diving into documentation, I just described the problem. Within seconds:

import { MDXRemote } from 'next-mdx-remote/rsc';
import { highlight } from 'sugar-high';

const components = {
  code: ({ children, ...props }: any) => {
    const codeHTML = highlight(children);
    return <code dangerouslySetInnerHTML={{ __html: codeHTML }} {...props} />;
  },
};

export async function MDXContent({ source }: { source: string }) {
  return <MDXRemote source={source} components={components} />;
}

Problem solved. The AI understood the context, knew about syntax highlighting libraries, and implemented a solution that worked with Next.js 15's Server Components.

Component Architecture

We established a pattern for components:

// app/components/ArticleCard.tsx
interface ArticleCardProps {
  article: Article;
  variant?: 'default' | 'featured';
}

export function ArticleCard({ 
  article, 
  variant = 'default' 
}: ArticleCardProps) {
  const isFeatured = variant === 'featured';
  
  return (
    <article className={/* ... */}>
      {/* ... */}
    </article>
  );
}

Each component:

  • Has a single responsibility
  • Is properly typed
  • Is reusable
  • Follows consistent patterns

What We Accomplished in Hour One

By the end of the first hour:

  • ✅ Project initialized with modern tooling
  • ✅ Clean directory structure
  • ✅ Type-safe content loading system
  • ✅ MDX processing with syntax highlighting
  • ✅ Basic component architecture
  • ✅ Everything working with Server Components

No tutorial following. No Stack Overflow diving. Just conversation and iteration.

The Key Realization

What hit me during this first hour: I wasn't just getting code written faster—I was learning as I went.

Every prompt response included context:

  • Why this pattern?
  • How does this work with Next.js 15?
  • What are the alternatives?

I wasn't blindly copying code. I was understanding it.

Coming Up Next

In Part 4, we'll dive deep into building the complete MDX processing pipeline with caching, optimization, and all the production-ready details.


Next in the Series

👉 Continue to Part 4: Building the MDX Pipeline

Learn how we built a production-ready content system with proper caching and error handling.


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