How I Built a Full Next.js Project Using AI Tools Step by Step: Complete Guide With Code
A complete Next.js 14 application built using AI tools at every stage from project setup through deployment. This is not a tutorial showing the happy path. It documents the tasks where AI saved hours, the tasks where it caused problems, the prompts that worked, and the exact setup that produced the highest-quality AI output throughout the build. Every code block is from the actual project.
Cursor
AI-first code editor used for all component creation, API routes, and multi-file features in this project
cursor.com
Claude
Used for architecture planning, database schema design, and code review across the project
claude.ai
Vercel
Deployment platform for Next.js with AI-assisted configuration through the Vercel dashboard
vercel.com
Marcus Webb
June 19, 2026
Quick Answer: A Next.js 14 project with App Router, TypeScript, Prisma, and NextAuth built in three weeks using Cursor with Claude as the primary AI. AI assistance saved approximately 31 hours compared to the manual estimate for the same scope. The biggest savings were in boilerplate setup, type generation, and repetitive CRUD operations. The biggest problems were in authentication edge cases and database relation complexity where AI-generated code required significant debugging. Every major step has working code and notes on where the AI help was real versus where it caused detours.
Project Scope and Stack
- Project type: task management application with teams, projects, and assigned tasks
- Stack: Next.js 14 App Router, TypeScript, Prisma ORM, PostgreSQL, NextAuth.js, Tailwind CSS, shadcn/ui
- Deployment: Vercel with PlanetScale for database
- Total build time: 22 days across 3 weeks
- AI-assisted hours tracked: 47 of the total 78 hours
- Estimated time saving from AI assistance: 31 hours based on manual estimates for same tasks
Step 1: Project Setup and Architecture Planning (Day 1)
# Project initialization โ standard Next.js setup
# AI was used for architecture decisions AFTER this step, not during
npx create-next-app@latest task-app \
--typescript \
--tailwind \
--app \
--src-dir \
--import-alias '@/*'
cd task-app
# Add Prisma
npm install prisma @prisma/client
npx prisma init
# Add NextAuth
npm install next-auth @auth/prisma-adapter
# Add shadcn/ui
npx shadcn-ui@latest initArchitecture planning was done with Claude before writing any code. The prompt described the feature requirements and asked for a folder structure recommendation with reasoning for each choice. The output was used as a reference document, not copied directly. Two of the five structure suggestions were rejected based on project-specific knowledge Claude did not have.
# Architecture Planning Prompt Used With Claude
---
Planning a Next.js 14 App Router project for a task management app.
Features: user auth, teams, projects, tasks with assignments and status.
Provide:
1. Recommended folder structure under src/ with reasoning
2. Which data relationships need Prisma relations vs IDs
3. Where to put server actions vs API routes for this use case
4. Auth strategy recommendation: NextAuth sessions vs JWT
Constraints:
- Single developer, so simplicity over scalability
- Deploying to Vercel, database on PlanetScale
- Want to avoid over-engineering at this stage
Do not write any code yet โ provide structure and reasoning only.
---Step 2: Database Schema With Prisma (Day 2)
The Prisma schema was generated with AI assistance using the architecture planning output as context. The generated schema required two revisions: the many-to-many relation between users and teams was missing a role field on the join table, and the task assignment model did not account for the case where a task is assigned to a team rather than an individual. Both gaps were caught by reading the generated schema carefully before running migrations.
// schema.prisma โ final version after AI generation and review
// Two fields were added manually: TeamMember.role and Task.teamId
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
relationMode = "prisma"
}
model User {
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime?
image String?
accounts Account[]
sessions Session[]
teamMembers TeamMember[]
assignedTasks Task[] @relation("AssignedTasks")
createdTasks Task[] @relation("CreatedTasks")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Team {
id String @id @default(cuid())
name String
slug String @unique
members TeamMember[]
projects Project[]
tasks Task[] // tasks assigned to team not individual
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model TeamMember {
id String @id @default(cuid())
userId String
teamId String
role String @default("member") // ADDED MANUALLY โ AI missed this
user User @relation(fields: [userId], references: [id])
team Team @relation(fields: [teamId], references: [id])
createdAt DateTime @default(now())
@@unique([userId, teamId])
@@index([userId])
@@index([teamId])
}
model Project {
id String @id @default(cuid())
name String
description String?
teamId String
team Team @relation(fields: [teamId], references: [id])
tasks Task[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([teamId])
}
model Task {
id String @id @default(cuid())
title String
description String?
status String @default("todo")
priority String @default("medium")
projectId String
project Project @relation(fields: [projectId], references: [id])
assigneeId String?
assignee User? @relation("AssignedTasks", fields: [assigneeId], references: [id])
teamId String? // ADDED MANUALLY โ for team assignments
team Team? @relation(fields: [teamId], references: [id])
creatorId String
creator User @relation("CreatedTasks", fields: [creatorId], references: [id])
dueDate DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([projectId])
@@index([assigneeId])
@@index([teamId])
@@index([creatorId])
}Step 3: Server Actions for CRUD Operations (Days 3 to 7)
Server actions were the highest-value AI task in the entire project. Each CRUD operation for tasks, projects, and teams followed the same pattern: get session, validate permission, execute database operation, revalidate path, return result. After the first action was written manually to establish the pattern, every subsequent action was generated by Cursor Composer using the first as context. The acceptance rate on server action generation was 91 percent.
// src/actions/tasks.ts
// First action written manually to establish pattern
// Subsequent actions AI-generated using this as context reference
'use server'
import { revalidatePath } from 'next/cache'
import { getServerSession } from 'next-auth'
import { authOptions } from '@/lib/auth'
import { prisma } from '@/lib/prisma'
import type { Task } from '@prisma/client'
type CreateTaskInput = {
title: string
description?: string
projectId: string
assigneeId?: string
priority?: string
dueDate?: Date
}
export async function createTask(input: CreateTaskInput) {
const session = await getServerSession(authOptions)
if (!session?.user?.id) {
throw new Error('Unauthorized')
}
// Verify user has access to the project
const project = await prisma.project.findFirst({
where: {
id: input.projectId,
team: { members: { some: { userId: session.user.id } } }
}
})
if (!project) {
throw new Error('Project not found or access denied')
}
const task = await prisma.task.create({
data: {
...input,
creatorId: session.user.id,
status: 'todo'
},
include: {
assignee: { select: { id: true, name: true, image: true } },
creator: { select: { id: true, name: true, image: true } }
}
})
revalidatePath(`/projects/${input.projectId}`)
return { success: true, task }
}
export async function updateTaskStatus(
taskId: string,
status: 'todo' | 'in_progress' | 'done'
) {
const session = await getServerSession(authOptions)
if (!session?.user?.id) throw new Error('Unauthorized')
// Verify access through project team membership
const task = await prisma.task.findFirst({
where: {
id: taskId,
project: { team: { members: { some: { userId: session.user.id } } } }
},
include: { project: true }
})
if (!task) throw new Error('Task not found or access denied')
const updated = await prisma.task.update({
where: { id: taskId },
data: { status }
})
revalidatePath(`/projects/${task.projectId}`)
return { success: true, task: updated }
}Where AI Help Caused Detours
NextAuth configuration was the most problematic AI-assisted area. Three different AI-generated NextAuth configurations failed before a working one was produced. The failures all involved the same pattern: the AI generated configurations that worked for simple email-password auth but did not handle the specific combination of Prisma adapter, multiple OAuth providers, and session strategy correctly. The working configuration was eventually produced by showing Claude the exact error from the console and the exact NextAuth version being used. The lesson was that auth configuration requires the error message context, not just the goal.
Final Thoughts
Building a Next.js project with AI tools in 2026 is significantly faster than building without AI on the categories of work that are pattern-based: schema generation, CRUD server actions, component structure, TypeScript types, and repetitive API routes. The time saving of 31 hours on this project was real and came from those specific areas. The detours in authentication and complex database relations consumed approximately 9 hours โ meaning the net saving was roughly 22 hours. Knowing which tasks to hand to AI and which to own manually is the skill that determines whether the net result is positive.