Vibe coding a new blog • Anything

Vibe coding a new blog

Our blog was overdue. So I spent the weekend vibe coding it.

If you're new to "vibe coding," Andrej Karpathy coined the term:

"There's a new kind of coding I call 'vibe coding', where you fully give in to the vibes, embrace exponentials [of AI], and forget that the code even exists. [...] I'm building a project or webapp, but it's not really coding—I just see stuff, say stuff, run stuff, and copy paste stuff, and it mostly works."

At Create, we've taken vibe coding to its logical extreme since we launched in 2023. Our platform lets anyone build entire apps without touching code at all—no dependencies, no deployment headaches, just pure vibes.

The common criticism: that works great for new projects, but falls apart as codebases grow.

Create's main codebase is large. Can you vibe your way to a new feature?

I made it my weekend project to find out.

The Ghost of Blogs Past

When we started Create, we built our blog on Ghost. It worked fine initially, but quickly became a headache:

The biggest issue was Ghost's requirement for trailing slashes: true in next.config.js. We'd started with trailing slashes false and one does not simply change that for a blog. To get it to work we had to inspect the headers for html, but that broke og:images. A blog with no og:images is useless.

Here's a snippet from our old setup:

// next.config.js
const BLOG_URL = '[your-url]';

const nextConfig = {
  images: {
    domains: [\
      [your-ghost-domain],\
      // other domains...\
    ],
  },

async rewrites() {
    return [\
      {\
        source: '/blog/:path*',\
        has: [\
          {\
            type: 'header',\
            key: 'Accept',\
            value: 'text/html.*',\
          },\
        ],\
        destination: `${BLOG_URL}/:path*/`,  // Note the trailing slash\
      },\
      {\
        source: '/blog/:path*',\
        missing: [\
          {\
            type: 'header',\
            key: 'Accept',\
            value: 'text/html.*',\
          },\
        ],\
        destination: `${BLOG_URL}/:path*`,  // No trailing slash\
      },\
    ];
  },

headers() {
    return [\
      {\
        source: '/blog/:path*',\
        headers: [{ key: 'x-forwarded-host', value: 'www.create.xyz' }],\
      },\
    ];
  },
}

We needed something better.

Vibe Code to It Works

I decided to test Cursor, Windsurf, and Claude Code (IDEs vs. full app builders). I switched as each day someone launched more vibes. My approach was simple: point the AI at our web folder, give it my requirements, and let it brainstorm.

The AI suggested Sanity as a headless CMS for teammates. One glance at the docs convinced me. AI tools are becoming product recommenders—being AI-visible matters.

A couple hours of running yarn dev, pasting errors back and forth, grabbing keys, testing manually—and suddenly, we had an (ugly) blog. The AI nailed:

Here's a snippet of sanity query syntax I didn't even have to learn:

// Fetching posts from Sanity
const posts = await client.fetch('*[_type == "post"]{title, slug, author->{name}}');

Ambitious Vibe Coding: Local MDX-to-Sanity Converter

Sanity Studio was great for UI based edits, but I missed MDX files for code editor edits like we do for our docs site on Mintlify. Normally, I'd have to pick: Sanity or MDX. But vibe coding meant I didn't have to compromise.

The AI suggested building a custom local MDX-to-Sanity converter.

The first version wasn't perfect—but it gave me confidence. A bit more time, and it'd work. You get more ambitious when you don't have to write everything yourself (sometimes too ambitious)

Here's the core of what it built (ultimately a 1k LoC CLI):

// Convert MDX to PortableText
async function mdxToPortableText(content: string) {
  const lines = content.split('\n');
  const blocks: PortableTextBlock[] = [];

for (let line of lines) {
    if (line.startsWith('#')) {
      const level = line.match(/^#+/)?.[0].length || 1;
      blocks.push(createBlock(line.replace(/^#+\s/, ''), `h${level}`));
    }
    // Handle lists, code blocks, embeds...
  }

return blocks;
}

// Create Sanity post from MDX
async function createPost(mdxPath: string) {
  const content = await fs.readFile(mdxPath, 'utf8');
  const { frontmatter, content: mdxContent } = await parseMdx(content);
  const portableText = await mdxToPortableText(mdxContent);

const post: Post = {
    _type: 'post',
    title: frontmatter.title,
    slug: { current: slugify(frontmatter.title) },
    publishedAt: frontmatter.date,
    content: portableText,
  };

await client.create(post);
  watch(mdxPath, () => createPost(mdxPath)); // Live editing sync
}

By the end, we had full MDX support, easy embeds, and CLI commands for managing posts—all without manually writing a single line.

Vibe Coding Chaos

I was feeling good - until I looked at the codebase for the first time.

escher/
└── web/
    ├── app/
    │   ├── blog/
    │   │   ├── [slug]/page.tsx
    │   │   └── page.tsx
    │   ├── studio/                      # Sanity Studio embedded directly in Next.js route
    │   │   ├── sanity.cli.ts
    │   │   ├── sanity.config.ts
    │   │   ├── schemas/
    │   │   │   ├── author.ts
    │   │   │   ├── post.ts
    │   │   │   ├── category.ts
    │   │   │   ├── youtube.ts
    │   │   │   └── loom.ts
    │   │   └── package.json
    │   └── api/
    │       ├── sanity/route.ts          # Sanity API routes mixed with Next.js API
    │       └── blog-posts/route.ts
    ├── components/                      # Duplicate components at top-level
    │   ├── Feed.tsx                     # Different implementation from features/blog
    │   ├── PostCard.tsx                 # Different implementation from features/blog
    │   ├── AuthorCard.tsx
    │   ├── TableOfContents.tsx
    │   └── RelatedPosts.tsx
    ├── features/
    │   └── blog/
    │       ├── components/              # Duplicate components causing subtle bugs
    │       │   ├── BlogFeed.tsx         # Different implementation from top-level
    │       │   ├── PostCard.tsx         # Different implementation from top-level
    │       │   ├── PortableText.tsx
    │       │   └── AuthorLink.tsx
    │       ├── schemas/                 # Duplicate schemas again here
    │       │   ├── author.ts
    │       │   ├── post.ts
    │       │   └── category.ts
    │       └── utils.ts
    ├── lib/
    │   └── sanity-client.ts             # Sanity client setup duplicated here
    ├── next.config.mjs
    └── package.json                     # Random package changes

The AI duplicated definitions, created convoluted exports, and even deprecated unrelated packages.

Cleaning Up and Getting Ambitious (Again)

Cleaning up took longer than expected. I had to pause the vibes and actually think. After clean up, here's our organized structure:

Web (Next.js frontend):

apps/flux/web
├── src
│   ├── app
│   │   └── (main)
│   │       └── blog
│   │           ├── page.tsx                 # Blog homepage
│   │           ├── [slug]
│   │           │   └── page.tsx             # Individual blog post page
│   │           ├── sitemap.ts               # Sitemap generation
│   │           └── llms.txt
│   │               └── route.ts             # LLM-friendly blog index
│   ├── features
│   │   └── blog
│   │       ├── components
│   │       │   ├── BlogFeed.tsx
│   │       │   ├── PostCard.tsx
│   │       │   ├── PortableText.tsx
│   │       │   ├── RelatedPosts.tsx
│   │       │   └── TableOfContents.tsx
│   │       ├── api.ts                       # Sanity API integration
│   │       └── types.ts                     # Blog data types
│   └── lib
│       └── sanity.ts                        # Sanity client setup

Studio (Sanity CMS):

apps/flux/studio
├── sanity.config.ts                         # Sanity CMS configuration
├── posts                                    # MDX blog posts synced with Sanity
│   ├── vibe-coding-a-new-blog.mdx
│   └── images                               # Blog post images
└── scripts
    ├── post-cli.ts                          # MDX-to-Sanity sync tool
    └── import-ghost.ts                      # Ghost import script

Shared Sanity Schemas:

packages/sanity-schemas
└── src
    ├── post.ts                              # Blog post schema definition
    └── index.ts                             # Schema exports

But structure out of the way, I got ambitious again:

Final Thoughts

A few things on my mind from the experiment.

In six months, vibe coding will be unrecognizable—faster, smarter, and ready for prime time. At Create, we're building what makes that possible. You shouldn't need deep dev knowledge to vibe on real things.

If you're an engineer excited about pushing these boundaries— join us or follow along here on our new blog.