DEV Community

jesus manrique
jesus manrique

Posted on • Originally published at guayoyo.tech

Spec-Driven Development with Claude Code: Build a Nuxt App from Scratch — Part 1 of 3

Series: Spec-Driven Development with Claude Code — Part 1 · Part 2 · Part 3



66% of developers say their top AI frustration is code that's "almost right, but not quite" (Stack Overflow Developer Survey 2025). A Cloud Security Alliance study found 45% of AI-generated code introduced OWASP Top 10 vulnerabilities. An METR randomized controlled trial showed developers were 19% slower using AI tools — despite predicting a 24% speedup.

The model isn't the problem. Not knowing exactly what to ask for is the problem.

Tell Claude Code "build me a login with Supabase" and you get functional code. But does it use your project patterns? Does it handle errors the way you expect? Does it protect routes correctly? Is the answer "almost"?

Spec-Driven Development (SDD) fixes this with a simple principle: the specification is the primary artifact. Code is its compilation target.

What Is Spec-Driven Development?

SDD is a methodology where you write a structured specification — with goals, non-goals, acceptance criteria, and constraints — before generating a single line of code. The spec lives in your repository, versioned alongside the code. When something changes, you update the spec and regenerate.

"Instead of coding first and writing docs later, in spec-driven development, you start with a spec. This is a contract for how your code should behave." — Den Delimarsky, GitHub Blog

This isn't waterfall. It's giving your AI agent a map before dropping it in the jungle.

SDD vs Vibe Coding:

Vibe Coding Spec-Driven Development
Primary artifact The prompt The specification
Source of truth Generated code The spec
Best for Prototypes, exploration Auth, payments, data, production
Failure mode Pattern drift, "almost right" Over-specification (mitigable)
Iteration loop Re-prompt until it works Edit spec → regenerate code

As RemyBuilds puts it: "Structure beats vibes."

What You'll Build

In this 3-part series, we'll build Catálogo Guayoyo — a simple but complete app:

  • ✅ Email/password login (Supabase Auth)
  • ✅ Product catalog via API
  • ✅ Protected routes with middleware
  • ✅ Tailwind CSS UI
  • ✅ Production deploy

All using Nuxt 3, Supabase, Tailwind CSS, and Claude Code as your development partner — following the SDD workflow.

Prerequisites

  • Node.js 20+
  • Claude Code CLI installed and authenticated
  • A free Supabase account
  • VS Code (recommended, not required)
  • A willingness to stop guessing

Step 1: Initialize the Nuxt Project

Open your terminal:

npx nuxi@latest init catalogo-guayoyo
cd catalogo-guayoyo
npm install
Enter fullscreen mode Exit fullscreen mode

This creates a clean Nuxt 3 project. We won't install dependencies manually yet — we'll let Claude Code handle that when it reads the spec.

Step 2: CLAUDE.md — Your Project Constitution

Before writing a spec for any feature, you need a CLAUDE.md: the file Claude Code reads at the start of every session, defining your project's non-negotiable rules.

Create CLAUDE.md at the project root:

# CLAUDE.md — Catálogo Guayoyo

## Stack
- Frontend: Nuxt 3 (Vue 3 Composition API + TypeScript)
- Styling: Tailwind CSS
- Backend/Auth: Supabase (auth + database + API)
- Hosting: Vercel (recommended)

## Architecture Rules
- Always use Composition API with `<script setup lang="ts">`
- All Supabase calls use the `@supabase/supabase-js` client
- Environment variables go in `.env` (never hardcoded)
- Protected routes use Nuxt middleware (`defineNuxtRouteMiddleware`)
- Generate types from Supabase database (`supabase gen types`)

## Code Style
- Strict TypeScript (`strict: true` in tsconfig)
- Component names: PascalCase
- Composables: `use` prefix (e.g., `useAuth`, `useProducts`)
- No `any` — if unsure of the type, use `unknown` and narrow

## What We DON'T Do
- No Options API
- No axios (use Nuxt's `$fetch` or Supabase client)
- No other auth providers (Supabase Auth only)
- No separate API servers — Supabase is the backend
Enter fullscreen mode Exit fullscreen mode

This file is your first act of SDD. You're telling Claude Code how to work before telling it what to build. Every future session inherits this context.

💡 Context Engineering: Shopify CEO Tobi Lütke and Andrej Karpathy coined this term within two days of each other in June 2025. It's "the delicate art and science of filling the context window with just the right information for the next step." CLAUDE.md is your first context engineering layer.

Step 3: Write the Specification

Now we write the spec for our app. Create specs/catalogo-guayoyo.md:

# Spec: Catálogo Guayoyo v1.0

## Goals
- [ ] User can register with email and password
- [ ] User can sign in and sign out
- [ ] Authenticated user sees a product catalog
- [ ] Products are fetched from an API
- [ ] Protected routes redirect to login if no session exists

## Non-Goals (out of scope)
- Password recovery (v2)
- Social login (Google, GitHub — v2)
- Shopping cart (v2)
- Admin dashboard (v2)
- User roles (v2)

## User Stories

### US-1: Registration
**As** a new visitor
**I want** to create an account with my email and password
**So that** I can access the product catalog

Acceptance criteria:
- [AC-1.1] Form with fields: email, password, confirm password
- [AC-1.2] Validation: valid email, password ≥ 8 chars, match
- [AC-1.3] On successful registration, redirect to /products
- [AC-1.4] If email already exists, show "This email is already registered"
- [AC-1.5] Passwords are NEVER displayed in plain text in the UI

### US-2: Sign In
**As** a registered user
**I want** to sign in with my email and password
**So that** I can view the product catalog

Acceptance criteria:
- [AC-2.1] Form with fields: email, password
- [AC-2.2] On sign in, redirect to /products
- [AC-2.3] On wrong credentials, show generic error
- [AC-2.4] Session persists on page reload

### US-3: Product Catalog
**As** an authenticated user
**I want** to see a list of products with name, price, and image
**So that** I can browse the catalog

Acceptance criteria:
- [AC-3.1] Responsive grid (1 col mobile, 2 tablet, 3 desktop)
- [AC-3.2] Each product shows: image, name, price, category
- [AC-3.3] Loading state while fetching data
- [AC-3.4] Empty state if no products exist
- [AC-3.5] Error state if API fails

### US-4: Sign Out
**As** an authenticated user
**I want** to sign out
**So that** I can protect my account

Acceptance criteria:
- [AC-4.1] "Sign out" button visible in navigation
- [AC-4.2] On sign out, redirect to /login
- [AC-4.3] Cannot access /products without session

### US-5: Protected Routes
**As** the system
**I want** to protect routes requiring authentication
**So that** only authenticated users access the catalog

Acceptance criteria:
- [AC-5.1] /products requires authentication
- [AC-5.2] /login and /register redirect to /products if already authenticated
- [AC-5.3] Reusable auth middleware (not per-page)

## Technical Constraints
- Framework: Nuxt 3 (Composition API + strict TypeScript)
- Styling: Tailwind CSS
- Auth: Supabase Auth (email/password provider)
- Data: `products` table in Supabase with fields: id, name, price, category, image_url, created_at
- Product API: REST endpoint from Supabase (`/rest/v1/products`)
- No external state management (no Pinia) — composables are sufficient
- Environment variables: `SUPABASE_URL`, `SUPABASE_ANON_KEY` in `.env`

## Data Schema

### Table: products
| Field | Type | Description |
|-------|------|-------------|
| id | uuid (PK) | Unique identifier |
| name | text | Product name |
| price | numeric(10,2) | Price in USD |
| category | text | Category (e.g., "Electronics") |
| image_url | text | Image URL |
| created_at | timestamptz | Creation date |
Enter fullscreen mode Exit fullscreen mode

Step 4: Claude Code Generates the Scaffold

With the spec written and CLAUDE.md ready, we ask Claude Code to generate the structure:

claude
Enter fullscreen mode Exit fullscreen mode

Inside Claude Code:

> Read @specs/catalogo-guayoyo.md and @CLAUDE.md. Based on the spec, generate the Nuxt 3 project scaffold: install dependencies (@supabase/supabase-js, @nuxtjs/tailwindcss), configure Tailwind, set up Supabase environment variables, and create the file structure with the components, pages, and composables we'll need. DON'T implement logic yet — just the scaffold.
Enter fullscreen mode Exit fullscreen mode

Claude Code reads both files and generates:

catalogo-guayoyo/
├── CLAUDE.md
├── specs/
│   └── catalogo-guayoyo.md
├── .env
├── nuxt.config.ts
├── pages/
│   ├── index.vue          # Redirects to /products or /login
│   ├── login.vue          # Login form
│   ├── registro.vue       # Registration form
│   └── productos.vue      # Product catalog
├── composables/
│   ├── useAuth.ts          # Authentication logic
│   └── useProducts.ts      # Product fetching
├── middleware/
│   └── auth.ts             # Route protection middleware
├── components/
│   ├── ProductCard.vue     # Individual product card
│   ├── ProductGrid.vue     # Product grid
│   └── AppHeader.vue       # Header with nav + logout
└── server/
    └── (empty — Supabase is our backend)
Enter fullscreen mode Exit fullscreen mode

What We Achieved in Part 1

In 20 minutes you went from a vague idea ("a login with products") to:

  • ✅ A Nuxt 3 project with strict TypeScript configured
  • ✅ A CLAUDE.md that permanently defines project rules
  • ✅ A complete specification with 5 user stories and 16 acceptance criteria
  • ✅ Tailwind CSS, Supabase client, and environment variables configured
  • ✅ File structure ready for implementation

And you still haven't written a single line of business logic. That's SDD: first you know what and how, then you write code. Not the other way around.

Note: Birgitta Boeckeler identifies three SDD maturity levels — spec-first, spec-anchored, and spec-as-source. We're at spec-first, the lightest and where most teams start. Write the spec before asking the AI for code. Simple and effective.


In Part 2: Auth & Data, we implement Supabase Auth, create the products table, and let Claude Code generate the data access layer with automatic TypeScript types.


References:

Top comments (0)