diff --git a/cSpell.json b/cSpell.json
index c102ce6856..d66c5bbf0f 100644
--- a/cSpell.json
+++ b/cSpell.json
@@ -124,7 +124,8 @@
"Dreamies",
"Treatos",
"Svetlana",
- "Queenie"
+ "Queenie",
+ "Linktree"
],
"ignoreWords": [
"Aiven",
@@ -170,7 +171,8 @@
"Sevalla's",
"Dataguide",
"justinellingwood",
- "Millis"
+ "Millis",
+ "Linktree"
],
"patterns": [
{
diff --git a/content/900-ai/index.mdx b/content/900-ai/index.mdx
index 223005e1d8..c299b91489 100644
--- a/content/900-ai/index.mdx
+++ b/content/900-ai/index.mdx
@@ -119,6 +119,24 @@ npx -y mcp-remote https://mcp.prisma.io/mcp
/>
+### Vibe Coding Tutorials
+
+Build complete, production-ready applications from scratch with AI assistance.
+
+
+
+ }
+ href={{
+ text: "Start building",
+ url: "/ai/tutorials/linktree-clone"
+ }}
+ body={`A complete vibe coding tutorial: build a full Linktree clone SaaS with Next.js, Prisma Postgres, and Clerk auth using AI assistance.`}
+/>
+
+
### Resources
diff --git a/content/900-ai/tutorials/linktree-clone.mdx b/content/900-ai/tutorials/linktree-clone.mdx
new file mode 100644
index 0000000000..25a26acdd6
--- /dev/null
+++ b/content/900-ai/tutorials/linktree-clone.mdx
@@ -0,0 +1,731 @@
+---
+title: 'Vibe Code a Linktree Clone SaaS with Next.js, Prisma & Clerk'
+metaTitle: 'Build a Linktree Clone SaaS with Next.js, Prisma Postgres, and Clerk Auth'
+description: 'A complete vibe coding tutorial: build a full Linktree clone SaaS application from scratch using Next.js, Prisma ORM, Prisma Postgres, and Clerk authentication with AI assistance.'
+sidebar_label: 'Linktree Clone (SaaS)'
+image: '/img/guides/prisma-linktree-saas-cover.png'
+completion_time: '45 min'
+community_section: true
+toc_max_heading_level: 3
+---
+
+## Introduction
+
+In this comprehensive vibe coding tutorial, you'll build a complete **Linktree clone SaaS** application from scratch using AI assistance. This guide teaches you how to leverage AI tools to rapidly develop a full-stack application with:
+
+- **[Next.js](https://nextjs.org/)** — React framework for production
+- **[Prisma ORM](https://www.prisma.io/orm)** — Type-safe database access
+- **[Prisma Postgres](https://www.prisma.io/postgres)** — Serverless PostgreSQL database
+- **[Clerk](https://clerk.com/)** — Authentication and user management
+
+By the end of this tutorial, you'll have a working SaaS application where users can sign up, create their profile, and manage their personal link page — all built with AI-assisted development.
+
+:::info What is Vibe Coding?
+
+Vibe coding is a development approach where you collaborate with AI assistants to build applications. You describe what you want to build, and the AI helps generate the code while you guide the direction and make architectural decisions.
+
+:::
+
+## Prerequisites
+
+Before starting this tutorial, make sure you have:
+
+- [Node.js 20+](https://nodejs.org) installed
+- A [Clerk account](https://clerk.com) (free tier works)
+- An AI coding assistant ([Cursor](https://cursor.com), [Windsurf](https://windsurf.com), [GitHub Copilot](https://github.com/features/copilot), etc.)
+- Basic familiarity with React and TypeScript
+
+:::note Recommended AI Models
+
+For best results, we recommend using the latest AI models such as (minimum) Claude Sonnet 4, Gemini 2.5 Pro, or GPT-4o. These models provide better code generation accuracy and understand complex architectural patterns.
+
+:::
+
+---
+
+## 1. Set Up Your Project
+
+Let's start by creating a new Next.js application:
+
+```terminal
+npx create-next-app@latest app-name
+```
+
+Once the setup is complete, you'll need to add **Prisma** and **Prisma Postgres** to your project. We've prepared a detailed prompt that handles the complete setup for you.
+
+👉 **Find the setup prompt here:** [Next.js + Prisma Prompt](/ai/prompts/nextjs)
+
+**How to use it:**
+
+1. Create a new file called `prompt.md` at the root of your project
+2. Copy and paste the prompt content into this file
+3. Ask your AI assistant to follow the instructions in this file
+
+The AI will set up Prisma ORM, create your database connection, and configure everything automatically.
+
+### Quick Check
+
+Let's verify everything is working correctly:
+
+1. Start your development server:
+ ```terminal
+ npm run dev
+ ```
+2. Open Prisma Studio to view your seed data:
+ ```terminal
+ npm run db:studio
+ ```
+
+If both commands run without errors and you can see sample data in Prisma Studio, you're ready to continue!
+
+:::tip Good Practice: Commit Early and Often
+
+Throughout this tutorial, we'll commit our changes regularly. This makes it easy to track progress and roll back if something goes wrong.
+
+Start by linking your project to GitHub:
+
+```terminal
+git init
+git add .
+git commit -m "Initial setup: Next.js app with Prisma"
+```
+
+:::
+
+---
+
+## 2. Set Up Authentication with Clerk
+
+Now let's add user authentication using [Clerk](https://clerk.com/), which provides a complete authentication solution out of the box.
+
+**Steps to follow:**
+
+1. Go to [Clerk](https://clerk.com/) and create an account (if you don't have one)
+2. Create a new application in your Clerk dashboard
+3. Follow Clerk's official quickstart guide to integrate it with your Next.js app:
+
+👉 **Clerk Next.js Quickstart:** [clerk.com/docs/nextjs/getting-started/quickstart](https://clerk.com/docs/nextjs/getting-started/quickstart)
+
+The guide will walk you through installing the SDK, adding environment variables, and wrapping your app with the `ClerkProvider`.
+
+Once complete, commit your changes:
+
+```terminal
+git add .
+git commit -m "Add Clerk authentication setup"
+```
+
+---
+
+## 3. Update Your Database Schema
+
+Since we're building a Linktree clone, we need to update the database schema to support our specific data model. This includes:
+
+- A `User` model with a unique `username` (for public profile URLs like `/username`)
+- A `Link` model to store each user's links
+
+Replace the contents of your `prisma/schema.prisma` file with the following:
+
+```prisma file=prisma/schema.prisma
+generator client {
+ provider = "prisma-client"
+ output = "../app/generated/prisma"
+}
+
+datasource db {
+ provider = "postgresql"
+}
+
+// Example User model for testing
+model User {
+ id Int @id @default(autoincrement())
+ email String @unique
+ username String @unique // Important for the public profile URL
+ clerkId String @unique // Links to Clerk Auth
+ name String?
+ links Link[]
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+}
+
+model Link {
+ id Int @id @default(autoincrement())
+ title String
+ url String
+ userId Int
+ user User @relation(fields: [userId], references: [id])
+ createdAt DateTime @default(now())
+}
+```
+
+Since we're changing the schema structure, we need to reset the database. The existing seed data was just for testing purposes, so it's safe to drop and recreate:
+
+```terminal
+npx prisma db push --force-reset
+```
+
+This command:
+- **Drops** the existing database tables
+- **Creates** new tables based on your updated schema
+
+:::warning Use with Caution
+
+The `--force-reset` flag deletes all existing data. This is fine during prototyping, but never use it on a production database! Once your schema is stable, switch to `prisma migrate dev` for proper migration tracking.
+
+:::
+
+### Quick Check
+
+Open Prisma Studio to verify the new schema is applied:
+
+```terminal
+npm run db:studio
+```
+
+You should see the updated `User` and `Link` tables (they'll be empty, which is expected).
+
+**Commit your changes:**
+
+```terminal
+git add .
+git commit -m "Update schema for Linktree clone"
+```
+
+---
+
+## 4. Connect Clerk Users to Your Database
+
+Here's the challenge: when a user signs in with Clerk, they exist in Clerk's system but **not** in your database. We need to bridge this gap.
+
+Our approach: create a "Claim Username" flow where users pick their unique username (e.g., `yourapp.com/johndoe`) after signing in for the first time.
+
+:::info Use ASK Mode First
+
+When working with AI assistants, we recommend using **ASK mode** by default to review suggested changes before applying them. Only switch to AGENT mode once you're comfortable with the proposed code.
+
+:::
+
+### The Prompt
+
+Copy and paste the following prompt into your AI assistant:
+
+````markdown
+Connect Clerk authentication to your Prisma database with a "Claim Username" flow.
+
+**Goal:**
+
+When a user signs in via Clerk, they don't automatically exist in YOUR database. Create a flow where:
+
+1. Logged out → Show landing page with "Sign In" button
+2. Logged in but no DB profile → Show "Claim Username" form
+3. Has DB profile → Show dashboard
+
+**User Model (already in schema):**
+
+model User {
+ id Int @id @default(autoincrement())
+ email String @unique
+ username String @unique
+ clerkId String @unique
+ name String?
+ links Link[]
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+}
+
+**Files to create/update:**
+
+1. `app/actions.ts` - Server Action with `claimUsername(formData)`
+2. `app/page.tsx` - Three-state UI (logged out / claim username / dashboard)
+3. `app/api/users/route.ts` - Update POST to accept `clerkId`, `email`, `username`, `name`
+
+**Requirements:**
+
+- Use `'use server'` directive in `app/actions.ts`
+- Use `currentUser()` from `@clerk/nextjs/server` to get auth user
+- Store `clerkId`, `email`, `username`, and `name` in User model
+- Use `redirect("/")` after successful profile creation
+- Handle username uniqueness (Prisma will throw if duplicate)
+
+**Pattern:**
+
+1. Server Action receives FormData, validates username (min 3 chars, alphanumeric + underscore)
+2. Creates User in Prisma with Clerk's `user.id` as `clerkId`
+3. Page.tsx checks: `currentUser()` → then `prisma.user.findUnique({ where: { clerkId } })`
+4. Render different UI based on auth state and DB state
+
+**Keep it simple:**
+
+- No middleware file needed
+- No webhook sync (user creates profile manually)
+- Basic validation (username length >= 3)
+- Errors just throw (no fancy error UI for MVP)
+````
+
+After the AI generates the code, you may see TypeScript errors. This is because the Prisma Client needs to be regenerated to reflect the schema changes:
+
+```terminal
+npx prisma generate
+```
+
+### Quick Check
+
+Test the complete flow:
+
+1. Stop your dev server and restart it
+2. Open your app in the browser
+3. Sign up as a new user through Clerk
+4. You should see the "Claim Username" form
+5. Enter a username and submit
+6. Verify the user appears in Prisma Studio (`npm run db:studio`)
+
+If everything works, commit your changes!
+
+---
+
+## 5. Upgrade the UI Design
+
+Let's give our app a more polished, friendly look inspired by platforms like Buy Me a Coffee.
+
+👉 [Visit Buy Me a Coffee for design inspiration](https://buymeacoffee.com/)
+
+Copy and paste this prompt to your AI assistant:
+
+````markdown
+Design a minimal, friendly UI inspired by Buy Me a Coffee.
+
+**Theme:**
+- Force light mode only (no dark mode switching)
+- Clean white background (#FFFFFF)
+- Black text (#000000) for headings
+- Gray (#6B7280) for secondary text
+- Bright yellow (#FFDD00) for CTA buttons
+- Light gray (#F7F7F7) for cards/sections
+- Subtle borders (#E5E5E5)
+
+**Typography & Spacing:**
+- Large, bold headlines (text-5xl or bigger)
+- Generous whitespace and padding
+- Rounded corners everywhere (rounded-full for buttons, rounded-xl for cards)
+
+**Buttons:**
+- Primary: Yellow background, black text, rounded-full, font-semibold
+- Secondary: White background, border, rounded-full
+
+**Overall feel:**
+- Friendly, approachable, not corporate
+- Minimal — only essential elements
+- Mobile-first with good touch targets (py-4, px-8 on buttons)
+- One unified canvas — background applies to the entire page (body), with white cards floating on top. No separate section backgrounds.
+
+Use Tailwind CSS. Keep it simple.
+````
+
+### Quick Check
+
+After the AI applies the changes:
+
+1. Refresh your app and browse through all pages
+2. Verify the design has updated but **no functionality has changed**
+3. Test the sign-in flow and username claim process
+
+Once verified, commit the changes:
+
+```terminal
+git add .
+git commit -m "Update UI design"
+```
+
+---
+
+## 6. Build Link Management (Add & Delete)
+
+Now let's add the core functionality: managing links! Users should be able to add new links and delete existing ones from their dashboard.
+
+Copy and paste this prompt:
+
+````markdown
+Build a simple dashboard for managing links using Next.js App Router and Server Actions.
+
+**Requirements:**
+- Server Component page that fetches user data from database
+- "Add Link" form with Title and URL inputs
+- List of existing links with Delete button
+- Use Server Actions (no API routes) for create/delete operations
+- Use `revalidatePath("/")` after mutations to refresh the page
+
+**Pattern:**
+1. Create server actions in `actions.ts` with `'use server'` directive
+2. Pass actions directly to form `action` prop
+3. Keep page.tsx as a Server Component (no 'use client')
+4. Use hidden inputs for IDs (e.g., ``)
+
+**Keep it simple:**
+- No loading states
+- No client components
+- No confirmation dialogs
+- Just forms + server actions + revalidation
+
+This is the MVP pattern for CRUD with Next.js App Router.
+````
+
+### Quick Check
+
+Test the link management:
+
+1. Add a new link with a title and URL
+2. Verify it appears in your dashboard
+3. Delete the link
+4. Verify it's removed
+
+Both operations should work instantly without page navigation.
+
+---
+
+## 7. Create Public Profile Pages
+
+This is the heart of a Linktree clone: public profile pages that anyone can visit at `/username`.
+
+Copy and paste this prompt:
+
+````markdown
+Build a public profile page at /[username] using Next.js App Router dynamic routes.
+
+**Requirements:**
+- Create `app/[username]/page.tsx` as a Server Component
+- Fetch user + links from database by username (from URL params)
+- Return 404 if user not found (use `notFound()` from next/navigation)
+- Display: avatar (first letter), username, name, and list of links
+- Links open in new tab with `target="_blank"`
+- Add a small "Create your own" link at the bottom
+
+**Pattern:**
+1. Get params: `const { username } = await params`
+2. Query database with `findUnique({ where: { username } })`
+3. If no user: call `notFound()`
+4. Render profile with links as clickable buttons
+
+**Keep it simple:**
+- No auth required (it's a public page)
+- Pure Server Component (no 'use client')
+- Basic styling with hover effects
+
+This is the core "Linktree" feature — anyone can visit /username to see the links.
+````
+
+### Quick Check
+
+Test your public profile:
+
+1. Navigate to `localhost:3000/your-username` (replace with your actual username)
+2. Verify your profile and links display correctly
+3. Click a link and confirm it opens in a new tab
+
+---
+
+## 8. Add a "Copy Link" Button
+
+Make it easy for users to share their profile URL with a one-click copy button.
+
+Copy and paste this prompt:
+
+````markdown
+**Requirements:**
+
+- Create a Client Component (`'use client'`) for the button
+- Use `navigator.clipboard.writeText(url)` to copy
+- Show "Copied!" feedback for 2 seconds after clicking
+- Use `useState` to toggle the button text
+
+**Pattern:**
+
+1. Create `app/components/copy-button.tsx` with 'use client'
+2. Accept `url` as a prop
+3. On click: copy to clipboard, set `copied` to true
+4. Use `setTimeout` to reset after 2 seconds
+5. Import and use in your Server Component page
+
+**Keep it simple:**
+
+- One small client component
+- No toast libraries
+- Just inline text feedback ("Copy link" → "Copied!")
+````
+
+### Quick Check
+
+1. Find the "Copy link" button on your dashboard
+2. Click it and verify it shows "Copied!"
+3. Paste somewhere to confirm the URL was copied correctly
+
+---
+
+## 9. Create a Custom 404 Page
+
+When someone visits a non-existent username, they should see a friendly error page instead of a generic 404.
+
+Copy and paste this prompt:
+
+````markdown
+Create a custom 404 page for Next.js App Router.
+
+**Requirements:**
+- Create `app/not-found.tsx` (Server Component)
+- Display: 404 heading, friendly message, "Go home" button
+- Match your app's design (colors, fonts, spacing)
+
+**Pattern:**
+- Next.js automatically uses `not-found.tsx` when `notFound()` is called
+- Or when a route doesn't exist
+- No configuration needed — just create the file
+
+**Keep it simple:**
+- Static page, no data fetching
+- One heading, one message, one link
+- Same styling as rest of the app
+````
+
+### Quick Check
+
+Test the 404 page by visiting a random URL like `/this-user-does-not-exist`. You should see your custom 404 page with a link back to the homepage.
+
+---
+
+## 10. Add a Custom Background
+
+Let's make the app more visually distinctive with a custom background pattern.
+
+**First**, either:
+- Download a background SVG pattern you like, or
+- Create your own using tools like [SVG Backgrounds](https://www.svgbackgrounds.com/) or [Hero Patterns](https://heropatterns.com/)
+
+**Then**, save it as `background.svg` in your `public/` folder.
+
+Copy and paste this prompt:
+
+````markdown
+Add a custom SVG background to my app.
+
+**Requirements:**
+- The svg file is in the `public/` folder (e.g., `public/background.svg`)
+- Apply it as a fixed, full-cover background on the body
+
+**Pattern:**
+In `globals.css`, update the body:
+
+```css
+body {
+ background: var(--background) url('/background.svg') center/cover no-repeat fixed;
+ min-height: 100vh;
+}
+```
+
+**Key properties:**
+- `center/cover` — centers and scales to fill
+- `no-repeat` — prevents tiling
+- `fixed` — background stays in place when scrolling
+
+Files in `public/` are served at the root URL, so `/background.svg` works.
+````
+
+### Quick Check
+
+1. Refresh your app
+2. Verify the background appears on **all pages** (homepage, dashboard, profile pages, 404)
+3. If the background doesn't appear everywhere, ask your AI to fix it
+
+Commit your changes once it's working correctly.
+
+---
+
+## 11. Add Glassmorphism Card Containers
+
+Create visual depth by adding semi-transparent card containers that "float" over the background.
+
+Copy and paste this prompt:
+
+````markdown
+Add a reusable card container class to create visual separation from the background.
+
+**Requirements:**
+
+- Create a `.card` class in `globals.css`
+- Apply glassmorphism: semi-transparent white + blur
+- Use on all main content areas (landing, forms, dashboard, profile pages)
+
+**Pattern:**
+In `globals.css`, add:
+
+```css
+.card {
+ background: rgba(255, 255, 255, 0.9);
+ backdrop-filter: blur(10px);
+ border-radius: 1.5rem;
+ padding: 2rem;
+ box-shadow: 0 4px 24px rgba(0, 0, 0, 0.06);
+}
+```
+
+**Usage:**
+Wrap content sections with `
...
`
+
+For public profile pages (/[username]):
+Wrap the entire profile (avatar, name, username, and links list) in a single .card container
+This creates a Linktree-style floating card effect
+Footer/attribution links stay outside the card
+
+Hero section:
+Add a soft radial glow behind the content (large blurred white circle, blur-3xl, 50% opacity)
+No visible container edges — just organic, fading brightness
+Content floats freely over the glow
+
+**Result:**
+
+- Content "lifts" off the background
+- Subtle blur creates depth
+- Consistent UI across all pages
+````
+
+---
+
+## 12. Display Clerk Profile Images
+
+If users sign in with Google or another OAuth provider, Clerk stores their profile photo. Let's display it on public profiles!
+
+Copy and paste this prompt:
+
+````markdown
+On the public profile page (`/[username]`), display the user's Clerk profile image (Google photo, etc.) instead of the initial letter avatar.
+
+**Pattern:**
+
+```typescript
+// Fetch Clerk user to get profile image
+const client = await clerkClient()
+const clerkUser = await client.users.getUser(user.clerkId)
+```
+
+**Display:**
+- Use a plain `` tag (not Next.js Image component)
+- If `clerkUser.imageUrl` exists, show the image
+- Otherwise fallback to the yellow initial avatar
+
+**Keep it simple:**
+- No try/catch — let errors bubble up
+- No next.config changes needed
+- No database schema changes needed
+````
+
+### Quick Check
+
+Visit your public profile page and verify your profile image (from Google, GitHub, etc.) is displayed instead of the initial letter avatar.
+
+---
+
+## 13. Add Icons with Lucide
+
+Small icons can significantly improve UI clarity. Let's add some using Lucide React.
+
+Copy and paste this prompt:
+
+````markdown
+Add Lucide React icons to improve the UI.
+
+First install: npm install lucide-react
+
+Add icons to these elements:
+- View button: ExternalLink icon
+- Delete button: Trash2 icon (replace text with icon)
+- Empty links state: Link icon
+
+Import icons from 'lucide-react' and use with size prop (e.g., size={18}).
+
+Keep buttons minimal — only add icons where they improve clarity.
+````
+
+### Quick Check
+
+Browse through your app and verify the icons appear on:
+- The view/external link buttons
+- The delete buttons
+- The empty state when no links exist
+
+---
+
+## 14. Deploy to Vercel
+
+Time to ship! Let's deploy your app to Vercel.
+
+:::warning Important Steps
+
+Follow these steps carefully to avoid deployment errors.
+
+:::
+
+### Step 1: Configure Prisma for Production
+
+Add a `postinstall` script to ensure Prisma Client is generated during deployment.
+
+Add this to your `package.json` scripts section:
+
+```json file=package.json
+{
+ "scripts": {
+ "postinstall": "prisma generate"
+ }
+}
+```
+
+📖 **Reference:** [Deploy to Vercel - Build Configuration](/orm/prisma-client/deployment/serverless/deploy-to-vercel#build-configuration)
+
+### Step 2: Clean Up Development Files
+
+Delete the `scripts/` folder if it exists. This folder was auto-generated during initial setup for seed data, you don't need it in production.
+
+### Step 3: Deploy to Vercel
+
+1. Push your code to GitHub (if you haven't already)
+2. Go to [vercel.com](https://vercel.com) and import your repository
+3. **Important:** Add all your environment variables in Vercel's dashboard:
+ - `DATABASE_URL`
+ - `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY`
+ - `CLERK_SECRET_KEY`
+
+### Step 4: Update the App URL
+
+After your first deployment:
+
+1. Copy your production URL from Vercel (e.g., `https://your-app.vercel.app`)
+2. Add a new environment variable in Vercel:
+ ```env
+ NEXT_PUBLIC_APP_URL=https://your-app.vercel.app
+ ```
+3. Redeploy to apply the change
+
+:::warning Don't Forget This Step
+
+If you skip setting `NEXT_PUBLIC_APP_URL`, features like the "Copy Link" button will copy `localhost` URLs instead of your production URL.
+
+:::
+
+### Final Check
+
+Test your deployed app thoroughly:
+
+- [ ] Sign up flow works
+- [ ] Username claiming works
+- [ ] Adding/deleting links works
+- [ ] Public profile pages load correctly
+- [ ] Copy link copies the correct production URL
+- [ ] 404 page displays for non-existent usernames
+
+**Congratulations! Your Linktree clone is live! 🎉**
+
+---
+
+### Resources
+
+- [Prisma Documentation](/orm/overview/introduction)
+- [Next.js Documentation](https://nextjs.org/docs)
+- [Clerk Documentation](https://clerk.com/docs)
+- [Tailwind CSS Documentation](https://tailwindcss.com/docs)
diff --git a/sidebars.ts b/sidebars.ts
index 9590190de0..616d8bcc6f 100644
--- a/sidebars.ts
+++ b/sidebars.ts
@@ -494,6 +494,15 @@ const sidebars: SidebarsConfig = {
"ai/prompts/turborepo",
],
},
+ {
+ type: "category",
+ label: "Vibe Coding Tutorials",
+ collapsed: false,
+ collapsible: false,
+ items: [
+ "ai/tutorials/linktree-clone",
+ ],
+ },
{
type: "category",
label: "Coding Tools",