Day 01 — Tearing Down the WordPress House
Date: 2026-05-22
Status at end of day: Scaffold live. Payload admin at `/admin` returns 200. Zero pages built.
Why we're doing this
A friend of mine has been running her food blog on WordPress for years. It works, mostly. But "works, mostly" starts to feel like a cage when you look at what it costs — not just in dollars (hosting renewals that only go up) but in the invisible tax of a platform you don't control. Slow admin. Plugin soup. A theme that fights you every time you try to change something. And 90% of the traffic is mobile, but the site wasn't really built for that.
So we're rebuilding it from scratch. Custom Next.js app, Payload CMS, PostgreSQL, Tailwind CSS, Docker. The whole thing designed around how she actually uses it — she manages content herself and is technical enough to handle a real admin UI, she just doesn't want to write code.
And we're doing it with AI. Specifically, Claude Code doing most of the heavy lifting while I drive.
The planning phase: where AI genuinely earns its keep
Before a single line of code was written, we spent time in a design session. And this is where AI-assisted development surprised me the most on day one — not in the code writing, but in the decisions.
I had a rough idea of what I wanted. Claude asked questions one at a time. Not "what do you want to build?" but specific things: what's her content management workflow, how technically capable is she, what are the monetisation constraints, what does the traffic look like. Each question built on the last.
By the end of maybe 45 minutes of back-and-forth, we had:
- A full tech stack decision with reasoning for each choice
- A rendering strategy per page (SSG vs ISR with specific revalidation times)
- Payload CMS collections and globals defined
- API routes listed
- Infrastructure decisions made
- A 4-plan implementation sequence with a separate content migration script
That planning work lives in `docs/superpowers/specs/` and `docs/superpowers/plans/`. Reading it back, it's better documented than most professional projects I've worked on. The AI didn't just execute instructions — it pushed back, asked "why", and caught things I hadn't thought through.
One example: early in the session I told Claude I wanted to move off SiteGround — shared hosting we don't control, costs that only go up, a platform that doesn't fit what we're building. That instruction got swallowed somewhere in context compaction. Claude kept planning as if we were staying on SiteGround. I had to say it again before the hosting conversation actually happened.
Once it landed, we got to the right answer: Docker Compose + Caddy. The entire stack moves to any Linux box with a single `git pull` and `docker compose up`. SiteGround, a home server, a DigitalOcean droplet — doesn't matter. But I shouldn't have had to say it twice.
AI limitation: Context compaction is real. In a long planning session, earlier instructions can get dropped from the active window. If something matters — a constraint, a direction, a decision — say it again when it becomes relevant, or write it down in a plan that gets committed to disk.
AI advantage: Once the context was right, the planning quality was high. Architecture documentation, rendering strategies, API routes, Payload collections — all thought through and committed in one session. Better-structured than most professional projects I've seen.
Infrastructure: the decision I didn't know I needed to make
The hosting conversation I'd tried to have earlier — the one context compaction had eaten — finally happened properly. Claude laid out three paths: Nginx + PM2 on the existing VPS (familiar, simple), Docker Compose + Caddy (portable, handles SSL automatically), or a managed platform like Vercel (easiest but locks you to a vendor and gets expensive).
I chose Docker. Not because I had a strong view — because the reasoning was clear. With Docker Compose, moving the whole stack is: update a GitHub secret with the new server IP, push a commit, done. No reinstalling Nginx. No regenerating SSL certificates. No PM2 config to redo.
Caddy was a new one for me. It's a reverse proxy that automatically obtains and renews Let's Encrypt certificates. The entire SSL config is one line:
```
her-food-blog.com {
reverse_proxy app:3000
}
```
That's it. Caddy handles everything else. No Certbot. No cron jobs. No certificate renewal panic emails.
AI advantage: Surfacing options I wouldn't have compared side-by-side, explaining the trade-offs clearly, and being willing to recommend rather than just listing options neutrally.
Day one of actual building: where it got messy
With the plans written and committed, we started executing Plan 1 — scaffold the project, get the Payload admin running locally.
This is where the AI went from "impressive planner" to "frustrating sometimes, but honest about it."
Flop #1: The TTY problem
The first real task is running `create-payload-app` to scaffold the Next.js + Payload project. Claude tried to run it:
```bash
cd /tmp && npx create-payload-app@latest mlob-scaffold --template blank --db postgres --no-deps-install
```
Output:
```
■ TTY initialization failed: uv_tty_init returned EINVAL (invalid argument)
```
The CLI needs an interactive terminal to display its setup wizard. The AI's Bash tool doesn't have one — it runs commands in a non-interactive shell. Claude caught this immediately: "I can't run this one. You'll need to run it yourself in your terminal."
I ran it directly. It worked.
This is a recurring pattern with AI-assisted development. The AI is great at running build commands, file edits, git operations, TypeScript checks. Anything that needs a real TTY — interactive CLIs, things with fancy terminal UIs — it can't touch. You just have to know which category you're in.
AI flop: Silently failing on interactive CLIs. Not really Claude's fault — it's a fundamental constraint of how the tool works. But it's a speed bump you hit before you know to expect it.
Flop #2: The blank password problem
Once the scaffold was copied into the repo, we needed to start the PostgreSQL Docker container. Claude created the `docker-compose.yml` and ran:
```bash
docker compose up -d db
```
Warning in the output: `The "POSTGRES_PASSWORD" variable is not set. Defaulting to a blank string.`
The container started anyway... then immediately started restarting in a crash loop. Log:
```
Error: Database is uninitialized and superuser password is not specified.
You must specify POSTGRES_PASSWORD to a non-empty value for the superuser.
```
PostgreSQL refuses to start with a blank password. Fair enough. The problem was that we'd run `docker compose up` *before* creating the `.env` file.
Here's the thing that took a minute to debug: Docker Compose reads a `.env` file for variable substitution. Next.js reads `.env.local`. They're not the same file, and one does not substitute for the other. Claude had planned to create `.env.local` with the database credentials. What it had missed — and what wasn't in the original plan — is that `docker compose` needs its own `.env` file with `POSTGRES_PASSWORD` set.
So now we have two env files:
- `.env` — just `POSTGRES_PASSWORD=mlob-dev-password` (for Docker Compose)
- `.env.local` — everything Next.js needs (DATABASE_URI, PAYLOAD_SECRET, etc.)
Stop the container, delete the freshly-created volume (it was initialised with a blank password so it's poisoned), create both files, restart. Container came up healthy.
AI flop: Missing the two-env-file requirement in the original plan. Not wrong, exactly — Next.js projects commonly use only `.env.local`. But the interaction with Docker Compose's variable substitution was a gap. Claude caught it during debugging and documented it immediately so it won't happen again.
Flop #3: The misnamed environment variable
Container healthy, `.env.local` created, `npm run dev` running, browser open at `http://localhost:3000/admin`.
```
500 Internal Server Error
```
Dev server log:
```
Error: cannot connect to Postgres. Details: SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a string
```
"Client password must be a string" means the password reached the database driver as `undefined` — not a wrong password, but *no* password.
The `payload.config.ts` from the scaffold read `process.env.DATABASE_URL`. Our `.env.local` had `DATABASE_URI`. One letter difference: `URL` vs `URI`.
This is a maddening class of bug. The application starts fine, no startup errors, no warnings about missing environment variables — it just silently gets `undefined` from `process.env.DATABASE_URL` and tries to connect with that. The error only surfaces when the first database query runs.
The fix was one word:
```typescript
// Before (scaffold default)
connectionString: process.env.DATABASE_URL || '',
// After (matches our spec)
connectionString: process.env.DATABASE_URI || '',
```
Restart the dev server. `/admin` returned 200.
AI flop: The scaffold used `DATABASE_URL`; our plan specified `DATABASE_URI`. Claude generated the `.env.local` with `DATABASE_URI` (correct per our spec) without noticing the payload.config.ts disagreed. This is a subtle cross-file consistency issue — the kind that's easy to miss in a first-pass implementation and annoying to debug because nothing warns you.
The positive: once the error appeared, Claude diagnosed it correctly within one look at the logs and the config file. The debugging was fast even if the bug shouldn't have existed.
The win: Payload admin is live
After fixing the env variable, `http://localhost:3000/admin` loaded. Payload's first-run screen — create your admin user, set a password, log in. The admin dashboard appeared.
It's a bare scaffold at this point. Two collections (Users and Media), nothing else. But the plumbing works: Next.js talking to Payload, Payload talking to PostgreSQL in Docker, all running locally, all wired together correctly.
First commit of actual code: `feat: scaffold Next.js 15 + Payload CMS v3 project`. 26 files changed.
AI advantage: The systematic approach to debugging. When `/admin` returned 500, Claude didn't guess — it read the log, identified the exact error message, pulled up the relevant config file, and found the mismatch. The turnaround from "500 error" to "fix committed" was probably 10 minutes including the docker restart.
End of day: what's done, what's next
Done:
- Full architecture and implementation plans documented
- Next.js 15 + Payload CMS v3 scaffold committed
- PostgreSQL running in Docker
- Payload admin accessible at `/admin`
- Design tokens, gitignore, env file setup all sorted
Not done:
- Tailwind CSS (next session: Task 2)
- Fonts
- Any actual Payload collections for the real content (recipes, categories, workshops)
- Pages — zero public pages exist yet
Honest take on day one
The AI was genuinely useful. The planning phase was better with Claude than it would have been alone — more thorough, faster, better documented. The debugging was efficient. The code it wrote was clean.
But it makes mistakes. The two-env-file gap and the DATABASE_URL/URI mismatch are both things a careful human would have caught in a review. The TTY limitation is a real friction point for CLIs.
The pattern I'm settling into, and what I'll do differently going forward:
Detailed plans before any code. The superpowers planning workflow — structured implementation plans committed to disk before touching the codebase — is the highest-leverage thing in this setup. Not because Claude can't improvise, but because plans on disk survive context compaction. When a new session starts and the first thing Claude reads is the plan, decisions from three sessions ago are still there.
Memory between sessions. At the end of each session, ask Claude to save key decisions, gotchas, and context to memory files. That covers what plans don't — the "we renamed DATABASE_URL to DATABASE_URI and here's why" kind of context. Start each new session fresh; the memory carries what matters.
Small chunks, always verify. One task. One commit. One check before moving on. I ask verification questions at every step — what does the log actually say, what's the exact env var name, does this match what we planned. It slows the session down. It also catches the DATABASE_URL/URI class of bug before it becomes a midnight debugging session.
The AI isn't unreliable — it's stateless. Build your workflow around that fact and it's a genuinely useful collaborator. Don't and you'll spend half your time re-explaining decisions you already made.
Day2: Tailwind CSS v4 with design tokens, fonts, and the first real Payload collections.
This is part of an ongoing series documenting the rebuild of a friend's food blog from WordPress to a custom Next.js stack, built with AI assistance.

Comments
Post a Comment