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:
- It always felt separate from our main site
- Next.js integration was painful—especially hosting on
/blog
- It was expensive ($400/month for subdirectory hosting)
The biggest issue was Ghost's requirement for trailing slashes: true
in next.config.js
. We'd started with trailing slahes 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:
- Blog feed and individual post pages
- Sanity Studio embedded directly in Next.js
- Hours of tedious boilerplate code
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:
- Making it look like us: I pointed it at existing blogs I like and existing Create components in the repo.
- Table of Contents from markdown headers: Described behavior to AI, worked after a few shots. Helped to have done this before.
- OG:Image Generation: Automated fallback images. Never did this before, helped to have AI iterate through SVG generation.
- LLMs.txt & Sitemap Generation: It's more critical than ever AI can find your site and read content fast. So you can visit create.xyz/blog/vibe-coding-a-new-blog.md (or adding .md to any URL) and just get the text. I suspect we'll end up using this to teach Create about Create.
- Ghost Import: AI wrote a gnarly 2,000-line script converting the Ghost lexical formatted export to Sanity's PortableText. Lots of fun I didn't have to write it.
- Caching: It helped to know what this was to prompt right.
Final Thoughts
A few things on my mind from the experiment.
- Context matters: Existing codebase knowledge helped steer the AI. What UX and techniques give you perfect context at the right time?
- Multitasking: I set up git worktrees (James wrote the definitive post on this btw) so each vibe coding agent had its own environment.
- CI/CD & Linters: Our existing setup saved me a few times from doing anything too stupid — most new builders don't have the luxury of a cracked eng team to set this all up, which is why we build it into Create.
- AI vs. Human Structure: "trash" code from a human perspective still ran and the AI could navigate it. But no way was it passing PR review.
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.