An honest build log: how we went from a spoken brief to a portfolio in production with its own CMS — first designing the brand with Claude Design, then building and deploying it with Claude Code.
This document mixes the narrative (decisions, back-and-forth) with the technical (stack, structure, deploy, workflow). The idea is for it to both tell the story and let someone replicate it.
Before the code: the brand, designed with Claude
Nine years designing products, and still the designer's portfolio is the designer's worst project: too close, too personal, with no brief to save you from your own doubts. Instead of opening Figma and fighting a blank canvas, I opened a conversation with Claude Design and spoke to it the way I'd speak to a studio: who I am, what I admire, where I want to go.
I didn't write a prompt. I wrote a brief. I told it I'm a senior designer aiming at global teams, that my sensibility is minimalist, that I'm obsessed with Japanese art and the concept of Ma (間) — the space between things. The difference with a traditional tool was immediate: it didn't ask me to pick a template, it gave me back questions (one direction or several? novel or build on an existing system? how interactive?) — exactly the questions I ask my clients. The quality of the output was, exactly, the quality of the brief.
Three worlds in an afternoon, not three weeks
Instead of one proposal, Claude built three complete directions — each with its philosophy, typography, palette, components and an applied screen — and put them side by side on a single canvas to compare:
All three were defensible. That was the uncomfortable, revealing moment: I wasn't choosing between one good option and two fillers, I was curating. And curating —deciding what to leave out— is still the hardest and most human part of design.
I chose the quietest one — Ma. Not because it was the flashiest (it isn't), but because it said something true about how I work: I remove three things for every one I add. I asked for a single variant — that the palette breathe a little beyond vermilion — and it came back with an expanded but disciplined color system: each color with a defined usage budget (ochre 黄土 reserved for awards). It didn't invent colors at random: it extended the existing harmony.
From system to navigable site, without switching tools
This is where it stopped looking like an image generator. I asked for the full design system and it returned a functional site: home with a narrative scroll, a projects archive, a case study, about, journal, contact and —my favorite part— a page that documents the design system itself (type scale, tokens, components, rules). For the identity it built a lab with six logotypes —from an editorial wordmark to an LV monogram with a ligature, a vermilion hanko seal and a mark that made the concept of Ma literal— each tested large, at nav size, and reversed.
"The AI gave me three correct answers. Choosing which one was mine still depended on nine years of judgment."
The AI didn't give me the brand: I chose it from the options it offered. The tool filled the space of making; I kept the space of deciding. And that, in the end, is still where design lives.
The code's starting point: the "Ma" export
Out of that conversation came the concrete input for Claude Code: a Claude Design export — an HTML prototype (Ma System.html) that loaded React via CDN + Babel standalone and a few loose .jsx files.
The pieces of the visual system that arrived packaged:
- Washi (paper) palette: paper #f4f1ea, ink #1a1815, and accents spent like money — vermilion 朱 #9b3a2a, indigo 藍 #2d3f5c, matcha 抹茶 #6b7a4d, ochre 黄土 #a87f3a. - Typography: Fraunces (display serif), Inter Tight (UI sans), JetBrains Mono (metadata), Shippori Mincho + Noto Serif JP (kanji). - Interactions: custom cursor, scroll reveals, lines that "draw" themselves (hairline-draw), a quiet marquee.
The prototype was pretty but it wasn't a real site: no real content, no way to edit it, and no way to deploy it. That was Claude Code's job — and everything that follows.
The stack (and why)
| Piece | Choice | Why |
|---|---|---|
| Framework | Next.js 14 (App Router) | SSR, folder routing, React Server Components |
| UI | React 18 | What the prototype already used |
| Styles | Inline styles + tokens + a CSS layer | The prototype was inline; we respected it |
| Fonts | next/font/google | Auto self-host, no FOUT, CSS variables |
| Database | PostgreSQL on Neon | Serverless-friendly (key for Vercel) |
| ORM | Prisma 5 | Declarative schema, migrations, type-safety |
| Admin session | iron-session | Signed cookies, simple |
| Hosting | Vercel | Deploy from git, ideal for Next.js |
| Domain | Namecheap → DNS to Vercel | The domain was already bought |
Project structure
A recurring idea: content lives in the database, not hardcoded. Pages are export const dynamic = 'force-dynamic' and read from Prisma on each request. That way the admin panel can edit everything without touching code.
The CMS and the admin panel
Three models in schema.prisma:
- `Project` — projects and case studies. Includes fields for the configurable template: heroEm, heroTail, problemIntro, resultsIntro, heroImage, gallery (JSON), problems/approach/results/learnings (JSON strings). - `JournalPost` — articles: title, slug, date, subtitle, content (markdown), readMinutes. - `SiteContent` — key/value (JSON) pairs for editable site copy: hero, philosophy, trajectory, footer, about.
The panel lives at /admin (password login via iron-session). It has CRUD for projects and posts, and editing of site content. The API routes at /api/admin/* validate the session before writing.
Seed trick: prisma/seed.mjs uses upsert for each record, so running it many times never duplicates anything. And a deleteMany({ where: { slug: { notIn: [...] } } }) clears what's no longer in the seed. This lets us version the base content in git and re-seed on each deploy.
Deploy: Neon + Vercel + domain
6.1 Database (Neon)
1. Create a project at neon.tech → copy the connection string (postgresql://...?sslmode=require). 2. In schema.prisma, provider = "postgresql".
6.2 Connect to Vercel
1. Push the repo to GitHub. 2. Import the repo at vercel.com/new → detected framework: Next.js. 3. Environment variables (in all three environments): - DATABASE_URL → the Neon string - SESSION_SECRET → a long random string - ADMIN_PASSWORD → the panel password
The build script does the heavy lifting:
That is: generate the client, sync the schema with Neon, seed the content, and only then compile Next.
6.3 Domain (Namecheap)
In Advanced DNS, an A record @ → 76.76.21.21 (the IP Vercel gives you) + the verification TXT. It propagates in minutes; Vercel issues the SSL on its own.
Real content (no placeholders)
The prototype came with invented projects and testimonials. We replaced them with truthful content:
- Case studies (Alimarket / Alicorp and Apolo Design System / SimpliRoute) imported from a Webflow CSV export. The case-study template was made configurable (heroEm, problemIntro, etc. fields) instead of hardcoding the placeholder copy. - Bio and trajectory rewritten from the real CV (Valere, Banco Pichincha, Buk, Alicorp, SimpliRoute). - Honest cleanup: we removed an invented testimonial and fake awards the placeholder shipped with. A live portfolio shouldn't carry credentials that aren't real.
The Journal and a dependency-free markdown render
We imported real articles from the LinkedIn data export (Settings → Data Privacy → Get a copy of your data → Articles). The export is HTML; we extracted the text and turned it into markdown.
To show them we built:
1. The detail page journal/[slug]/page.tsx. 2. A custom markdown renderer (lib/markdown.tsx) — no libraries, to avoid touching the package-lock. It supports ##/###, > quotes, lists, and inline **bold**, *italic*, [links](url). The idea: parse by blocks (separated by blank lines) and then inline with a single regex.
And a reading-progress bar that fills as you scroll, pinned to the Nav (which is position: fixed) and measured with window.scrollY against the <article> bounds.
Responsive: one layer, marker classes
The prototype was all inline styles, where you can't put media queries. Instead of rewriting every component, we added a responsive layer in `globals.css` with marker classes and !important overrides:
Then we gave each key container its class (r-pad, r-stack, r-hero, r-2/3/4, r-row). Plus:
- A real hamburger nav (animated drawer) on mobile.
- The custom cursor turns off on touch (
@media (pointer: coarse)). - The decorative kanji shrinks so it doesn't cause horizontal scroll.
overflow-x: hidden, fluid images, an explicitviewport.
The workflow with Claude Code
The process was conversational, but backed by a very concrete technical loop: each natural-language request ended in a commit, and each commit in a deploy. The cycle, in detail:
1. Request → tight diff. Each instruction ("raise the image a bit", "drop the scroll background", "the title is too long") translated into a small, surgical change — most touching a single file. Keeping the diff tight makes the commit readable and a rollback trivial (git revert <sha>).
2. Local verification before pushing. The production DB (Neon) isn't reachable from the sandbox, so to test we ran the project against a temporary SQLite:
Confirming the markup with curl (instead of "looks fine") caught several false positives: things that rendered on first load but not after navigating.
3. Atomic, descriptive commits. One change = one commit, with a message that explains the why, not just the what:
The conventional commits prefix (feat/fix/content/chore) makes git log --oneline read like a changelog. The repo history tells the same story as this document — because it was written commit by commit.
4. Deploy by git. Push to main → Vercel picks up the commit and runs the build pipeline (prisma generate && prisma db push && seed && next build). No manual steps: the commit is the deploy. Each conversational request was in production in a couple of minutes, under its own SHA.
Closing
What began as a spoken brief became three worlds in an afternoon with Claude Design, then an HTML export with React via CDN, and ended up a Next.js site with its own CMS, real content, responsive, and in production under its own domain with Claude Code — all in a conversation, step by step.
Two tools, one same way of working: talk, not syntax. Claude Design filled the space of designing; Claude Code, the space of building. And in the middle, what no tool brings out of the box — the judgment, the what-to-leave-out — stayed mine.
The design system (Ma) was the compass: restraint. The same applied to the code — the simplest responsive layer, the dependency-free markdown render, the honest content. The space between things, here too, was what mattered.
Direction by Luis Venturi — Designed with Claude Design · built with Claude Code · luisventuri.com