Beginner ~3 hours

Build an AI Beauty Consultant

Create a personalized AI-powered beauty recommendations chatbot that analyzes skin types, suggests products, and builds custom skincare routines using Claude API, Next.js, and Supabase.

What We're Building

In this guide, you'll build a complete AI-powered beauty consultant chatbot that provides personalized skincare and beauty recommendations. The app will feature a conversational interface where users can discuss their skin concerns, get product suggestions, and receive customized skincare routines.

💬
Chat Interface Natural conversation with AI beauty expert
🧴
Product Recommendations AI-curated suggestions based on skin type
📋
Skin Analysis Questionnaire to determine skin profile
Routine Builder Personalized AM/PM skincare routines

By the end of this guide, you'll have a deployed application that beauty enthusiasts can use to get expert-level skincare advice powered by Claude's advanced language understanding.

Prerequisites

Before starting this guide, make sure you have the following:

  • Node.js 18+ installed on your machine
  • Anthropic API key - Sign up at console.anthropic.com
  • Supabase account - Free tier available at supabase.com
  • Vercel account - For deployment (optional but recommended)
  • Basic familiarity with React and TypeScript

Tech Stack Specification

Here's the technology stack we'll use for this build:

Layer Technology Why This Choice
Frontend Next.js 14, Tailwind CSS Modern React framework with built-in routing and excellent DX for building chat UIs
Backend Next.js API Routes Serverless functions that integrate seamlessly with the frontend
Database Supabase PostgreSQL with real-time subscriptions, perfect for storing user preferences and chat history
AI Claude API Best-in-class reasoning for nuanced beauty recommendations and skin analysis
Hosting Vercel Zero-config deployment with edge functions and excellent Next.js support

AI Agent Workflow

Here's how to leverage AI tools throughout this build to maximize productivity:

Project Scaffolding with Claude Code

Use Claude Code to quickly generate the project structure, components, and API routes. Here's how to prompt it effectively:

Prompt
# Example prompt for Claude Code
Create a Next.js 14 app with the following structure:
- Chat interface component with message bubbles
- Skin type questionnaire with multi-step form
- API route for Claude integration
- Supabase client setup for user preferences
- Tailwind CSS styling with pink/purple beauty theme

UI Generation with v0.dev

v0.dev is excellent for quickly generating beautiful chat interfaces and form components. Use it to create the visual foundation of your beauty consultant.

Pro Tip

When prompting v0.dev, include specific details like "beauty/skincare themed chat interface with gradient backgrounds, rounded message bubbles, and a product recommendation card component."

Development with Cursor

Cursor's AI features help you iterate quickly on the codebase. Use Cmd+K to generate code blocks, and Cmd+L to chat about implementation details. It's particularly useful for debugging Claude API integration issues.

Step-by-Step Build Guide

Phase 1: Chat Interface Setup

Start by creating the core chat interface. This will be the primary way users interact with the AI beauty consultant.

bash
# Create Next.js project
npx create-next-app@latest beauty-consultant --typescript --tailwind --app
cd beauty-consultant

# Install dependencies
npm install @anthropic-ai/sdk @supabase/supabase-js
tsx
// components/ChatInterface.tsx
import { useState } from 'react';

interface Message {
  role: 'user' | 'assistant';
  content: string;
}

export default function ChatInterface() {
  const [messages, setMessages] = useState<Message[]>([]);
  const [input, setInput] = useState('');
  const [isLoading, setIsLoading] = useState(false);

  const sendMessage = async () => {
    if (!input.trim()) return;

    const userMessage = { role: 'user', content: input };
    setMessages(prev => [...prev, userMessage]);
    setInput('');
    setIsLoading(true);

    const response = await fetch('/api/chat', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ messages: [...messages, userMessage] })
    });

    const data = await response.json();
    setMessages(prev => [...prev, { role: 'assistant', content: data.message }]);
    setIsLoading(false);
  };

  return (
    <div className="flex flex-col h-screen bg-gradient-to-br from-pink-50 to-purple-50">
      {/* Message display area */}
      <div className="flex-1 overflow-y-auto p-4 space-y-4">
        {messages.map((msg, i) => (
          <div key={i} className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`}>
            <div className={`max-w-[80%] p-4 rounded-2xl ${
              msg.role === 'user'
                ? 'bg-pink-500 text-white'
                : 'bg-white shadow-md'
            }`}>
              {msg.content}
            </div>
          </div>
        ))}
      </div>
      {/* Input area */}
      <div className="p-4 bg-white border-t">
        <div className="flex gap-2">
          <input
            value={input}
            onChange={(e) => setInput(e.target.value)}
            onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
            placeholder="Ask about skincare, products, routines..."
            className="flex-1 p-3 border rounded-xl focus:ring-2 focus:ring-pink-300"
          />
          <button
            onClick={sendMessage}
            disabled={isLoading}
            className="px-6 py-3 bg-pink-500 text-white rounded-xl hover:bg-pink-600"
          >
            Send
          </button>
        </div>
      </div>
    </div>
  );
}

Phase 2: User Preference Questionnaire

Create a multi-step questionnaire to gather user skin information. This data will be used to personalize recommendations.

tsx
// components/SkinQuiz.tsx
const questions = [
  {
    id: 'skinType',
    question: 'How would you describe your skin?',
    options: ['Oily', 'Dry', 'Combination', 'Normal', 'Sensitive']
  },
  {
    id: 'concerns',
    question: 'What are your main skin concerns?',
    options: ['Acne', 'Aging', 'Dark spots', 'Dryness', 'Redness', 'Texture'],
    multiple: true
  },
  {
    id: 'budget',
    question: 'What\'s your skincare budget?',
    options: ['Drugstore ($)', 'Mid-range ($$)', 'Luxury ($$$)', 'No limit']
  },
  {
    id: 'routine',
    question: 'How much time for your routine?',
    options: ['Quick (2-3 steps)', 'Moderate (4-5 steps)', 'Extensive (6+ steps)']
  }
];

Phase 3: Product Database Schema

Set up Supabase to store products and user preferences. This enables personalized recommendations that improve over time.

sql
-- Supabase SQL schema

-- User preferences table
CREATE TABLE user_preferences (
  id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
  user_id UUID REFERENCES auth.users,
  skin_type TEXT,
  concerns TEXT[],
  budget TEXT,
  routine_preference TEXT,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

-- Products table
CREATE TABLE products (
  id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
  name TEXT NOT NULL,
  brand TEXT,
  category TEXT,
  price_range TEXT,
  skin_types TEXT[],
  concerns TEXT[],
  ingredients TEXT[],
  description TEXT,
  image_url TEXT
);

-- Chat history for context
CREATE TABLE chat_history (
  id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
  user_id UUID REFERENCES auth.users,
  messages JSONB,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

Phase 4: AI Recommendation Engine

This is the core of your beauty consultant - the Claude API integration with specialized beauty prompts.

typescript
// app/api/chat/route.ts
import Anthropic from '@anthropic-ai/sdk';
import { NextResponse } from 'next/server';

const anthropic = new Anthropic({
  apiKey: process.env.ANTHROPIC_API_KEY!
});

const BEAUTY_SYSTEM_PROMPT = `You are an expert AI beauty consultant with deep knowledge of
skincare, makeup, and beauty routines. Your expertise includes:

- Dermatology-backed skincare recommendations
- Product ingredient analysis (retinoids, AHAs, BHAs, peptides, etc.)
- Customized routine building for AM and PM
- Understanding skin types, concerns, and how they interact
- Budget-conscious recommendations across all price points

Guidelines:
1. Always ask clarifying questions about skin type and concerns if not provided
2. Explain WHY you're recommending specific products or ingredients
3. Warn about potential irritants or ingredient conflicts
4. Suggest patch testing for new products
5. Be encouraging and supportive about skincare journeys

Format recommendations clearly with:
- Product name and brand
- Key ingredients and their benefits
- How to use (when in routine, frequency)
- Price range indicator ($, $$, $$$)`;

export async function POST(req: Request) {
  const { messages, userPreferences } = await req.json();

  // Build context from user preferences if available
  let contextPrompt = BEAUTY_SYSTEM_PROMPT;
  if (userPreferences) {
    contextPrompt += `\n\nUser Profile:
- Skin Type: ${userPreferences.skinType}
- Main Concerns: ${userPreferences.concerns?.join(', ')}
- Budget: ${userPreferences.budget}
- Routine Preference: ${userPreferences.routine}`;
  }

  const response = await anthropic.messages.create({
    model: 'claude-sonnet-4-20250514',
    max_tokens: 1024,
    system: contextPrompt,
    messages: messages.map((m: any) => ({
      role: m.role,
      content: m.content
    }))
  });

  const textContent = response.content.find(c => c.type === 'text');

  return NextResponse.json({
    message: textContent?.text || 'I apologize, I couldn\'t generate a response.'
  });
}

Phase 5: Skin Type Analysis Prompts

Create specialized prompts for analyzing user skin concerns and generating targeted recommendations.

typescript
// lib/beautyPrompts.ts

export const SKIN_ANALYSIS_PROMPT = `Analyze the user's skin profile and provide:

1. **Skin Type Assessment**
   - Primary type (oily, dry, combination, normal, sensitive)
   - Secondary characteristics
   - Seasonal variations to expect

2. **Concern Priority Ranking**
   - Rank their concerns from most to least addressable
   - Explain which concerns should be tackled first and why

3. **Ingredient Recommendations**
   - Must-have ingredients for their skin type
   - Ingredients to avoid
   - Power combinations that work well together

4. **Red Flags**
   - Signs that indicate they should see a dermatologist
   - Potential sensitivities to watch for`;

export const ROUTINE_BUILDER_PROMPT = `Create a personalized skincare routine:

**Morning Routine:**
1. Cleanser - [recommendation + why]
2. Toner/Essence - [if applicable]
3. Serum - [targeted treatment]
4. Moisturizer - [based on skin type]
5. SPF - [always!]

**Evening Routine:**
1. First Cleanse - [if wearing makeup/SPF]
2. Second Cleanse
3. Actives - [retinol, acids, etc. with frequency]
4. Serum
5. Night Cream/Sleeping Mask

**Weekly Treatments:**
- Exfoliation schedule
- Mask recommendations
- Special treatments

Include timing between steps and any layering order notes.`;

export const PRODUCT_COMPARISON_PROMPT = `Compare these products for the user:

For each product, analyze:
- Key active ingredients and their concentrations
- Best suited for which skin types
- Potential irritation factors
- Value for money
- How it fits into a routine

Provide a clear recommendation with reasoning.`;

export const INGREDIENT_ANALYSIS_PROMPT = `Analyze this ingredient list:

1. **Star Ingredients** - What's great about this formula
2. **Potential Irritants** - What might cause issues
3. **Missing Elements** - What could make it better
4. **Conflicts** - Ingredients that shouldn't be mixed
5. **Best Paired With** - Complementary products/ingredients

Rate the product's ingredient quality on a scale of 1-10 for:
- Effectiveness
- Gentleness
- Value
- Innovation`;

Phase 6: Routine Builder Feature

Implement the visual routine builder that displays personalized AM/PM skincare routines.

tsx
// components/RoutineBuilder.tsx
interface RoutineStep {
  order: number;
  category: string;
  product: string;
  timing: string;
  notes: string;
}

interface RoutineProps {
  morningRoutine: RoutineStep[];
  eveningRoutine: RoutineStep[];
}

export default function RoutineBuilder({ morningRoutine, eveningRoutine }: RoutineProps) {
  return (
    <div className="grid md:grid-cols-2 gap-6 p-6">
      {/* Morning Routine */}
      <div className="bg-gradient-to-br from-amber-50 to-orange-50 rounded-2xl p-6">
        <h3 className="text-xl font-bold text-amber-800 mb-4 flex items-center gap-2">
          ☀️ Morning Routine
        </h3>
        <div className="space-y-3">
          {morningRoutine.map((step, i) => (
            <div key={i} className="bg-white rounded-xl p-4 shadow-sm">
              <div className="flex items-center gap-3">
                <span className="w-8 h-8 bg-amber-100 rounded-full flex items-center justify-center text-amber-600 font-bold">
                  {step.order}
                </span>
                <div>
                  <p className="font-medium text-gray-800">{step.category}</p>
                  <p className="text-sm text-gray-600">{step.product}</p>
                </div>
              </div>
              {step.notes && (
                <p className="mt-2 text-xs text-gray-500 italic">{step.notes}</p>
              )}
            </div>
          ))}
        </div>
      </div>

      {/* Evening Routine */}
      <div className="bg-gradient-to-br from-indigo-50 to-purple-50 rounded-2xl p-6">
        <h3 className="text-xl font-bold text-indigo-800 mb-4 flex items-center gap-2">
          🌙 Evening Routine
        </h3>
        <div className="space-y-3">
          {eveningRoutine.map((step, i) => (
            <div key={i} className="bg-white rounded-xl p-4 shadow-sm">
              <div className="flex items-center gap-3">
                <span className="w-8 h-8 bg-indigo-100 rounded-full flex items-center justify-center text-indigo-600 font-bold">
                  {step.order}
                </span>
                <div>
                  <p className="font-medium text-gray-800">{step.category}</p>
                  <p className="text-sm text-gray-600">{step.product}</p>
                </div>
              </div>
              {step.timing && (
                <p className="mt-2 text-xs text-purple-600">⏱️ {step.timing}</p>
              )}
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

Common Issues & Solutions

Here are some common issues you might encounter and how to solve them:

Rate Limiting with Claude API

If you hit rate limits, implement exponential backoff in your API route. Consider caching common responses in Supabase to reduce API calls.

Long Response Times

Beauty recommendations can be detailed. Use streaming responses with the Anthropic SDK to show text as it's generated, improving perceived performance.

Supabase Connection Issues

Ensure your Supabase URL and anon key are correctly set in your .env.local file. Check that Row Level Security (RLS) policies allow the operations you need.

Next Steps

Congratulations on building your AI Beauty Consultant! Here are some ways to enhance it further:

  • Add image upload - Let users upload photos for visual skin analysis using Claude's vision capabilities
  • Integrate product APIs - Connect to Sephora, Ulta, or beauty brand APIs for real-time product data and pricing
  • Build a recommendation history - Track which products users tried and their results to improve future suggestions
  • Add authentication - Use Supabase Auth to save user profiles and preferences across sessions
  • Create shareable routines - Let users share their personalized routines via social media or direct link
Deploy to Production

Push your code to GitHub and connect to Vercel for instant deployment. Don't forget to add your environment variables (ANTHROPIC_API_KEY, SUPABASE_URL, SUPABASE_ANON_KEY) in Vercel's project settings.