Awesome-omni-skill ui-development

Generate production-ready Next.js projects with TypeScript, Tailwind CSS, shadcn/ui, and API integration. Use when the user asks to build, create, develop, or scaffold a Next.js application, web app, full-stack project, or frontend with backend integration. Prioritizes modern stack (Next.js 14+, TypeScript, shadcn/ui, axios, react-query) and best practices. Also triggers on requests to add features, integrate APIs, or extend existing Next.js projects.

install
source · Clone the upstream repo
git clone https://github.com/diegosouzapw/awesome-omni-skill
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/diegosouzapw/awesome-omni-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/development/ui-development" ~/.claude/skills/diegosouzapw-awesome-omni-skill-ui-development && rm -rf "$T"
manifest: skills/development/ui-development/SKILL.md
source content

UI Development

Generate production-ready Next.js projects from natural language, with shadcn/ui components, API integration, type safety, and modern tooling.

Quick Start (TL;DR)

Fast path for simple projects:

  1. Create Next.js app → 2. Install shadcn/ui → 3. Build UI → 4. Start with PM2 → 5. Screenshot review → 6. Done

Live preview: Projects run on PM2 (port 3002), accessible at

http://localhost:3002
or via nginx proxy if configured.

Default workflow: All projects use PM2 for dev server management (prevents port conflicts, ensures single instance).

Requirements & Optional Features

Required Dependencies

  • Node.js 18+ and npm/yarn/pnpm
  • Git (for project initialization)

Optional Features (user can decline)

1. Auto-Revision with Visual Review (requires Chromium)

  • What it does: Takes screenshots during development to visually review designs and auto-fix issues
  • Installation:
    sudo apt-get install chromium-browser
    (Debian/Ubuntu)
  • Privileges: Read/write access to project files, execute chromium in headless mode
  • If declined: Manual review only (you describe, user verifies)

2. Live Preview Server (requires Nginx)

  • What it does: Serves project on external port for live preview during development (useful for mobile testing or remote access)
  • Installation:
    sudo apt-get install nginx
  • How it works: PM2 runs dev server on port 3002, nginx proxies it to chosen external port
  • Nginx config template:
    # /etc/nginx/sites-available/<project-name>
    server {
      listen <external-port>;  # e.g., 3001, 8081, etc.
      server_name _;
      
      location / {
        proxy_pass http://localhost:3002;  # PM2 dev server
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
      }
    }
    
  • Enable:
    sudo ln -s /etc/nginx/sites-available/<project-name> /etc/nginx/sites-enabled/ && sudo systemctl reload nginx
  • If declined: Access directly via
    http://localhost:3002
    (PM2 port)

Before starting, ask user if they want to enable optional features.

Common Project Types

Quick reference for typical requests:

  • Dashboard/Admin Panel → Use
    (dashboard)
    route group, shadcn data tables, charts
  • Landing Page → Single
    app/page.tsx
    , hero section, features grid, testimonials
  • Todo/Task App → shadcn checkbox, input, button; local state or API
  • Blog/CMS → Dynamic routes
    app/blog/[slug]/page.tsx
    , markdown support
  • E-commerce → Product catalog, cart state (Zustand), checkout flow
  • SaaS App → Auth (
    (auth)
    group), protected routes, subscription logic
  • Portfolio → Projects grid, contact form, image gallery
  • Form-heavy App → React Hook Form + Zod validation, shadcn form components

Ask user: What type of project are you building? (helps determine structure and components)

Tech Stack

Core:

  • Next.js 14+ (App Router)
  • TypeScript
  • Tailwind CSS v3
  • shadcn/ui (recommended UI component library)
  • ESLint + Prettier

API Integration (default):

  • axios (HTTP client)
  • @tanstack/react-query (data fetching, caching, state management)

Optional (based on needs):

  • Zustand (client-side state management)
  • Zod (runtime validation)
  • next-auth (authentication)
  • Prisma (database ORM)

Project Structure

Industry-standard Next.js 14+ App Router structure with feature-based organization:

<project-name>/
├── app/                                # Next.js 14 App Router
│   ├── (auth)/                         # Route group (auth pages)
│   │   ├── login/
│   │   │   └── page.tsx
│   │   ├── register/
│   │   │   └── page.tsx
│   │   └── layout.tsx                  # Auth-specific layout
│   ├── (dashboard)/                    # Route group (protected pages)
│   │   ├── dashboard/
│   │   │   ├── page.tsx
│   │   │   └── loading.tsx
│   │   ├── profile/
│   │   │   └── page.tsx
│   │   ├── settings/
│   │   │   └── page.tsx
│   │   └── layout.tsx                  # Dashboard layout with sidebar
│   ├── api/                            # API routes
│   │   ├── auth/
│   │   │   └── [...nextauth]/route.ts
│   │   └── users/
│   │       └── route.ts
│   ├── layout.tsx                      # Root layout
│   ├── page.tsx                        # Home page
│   ├── loading.tsx                     # Root loading UI
│   ├── error.tsx                       # Root error boundary
│   ├── not-found.tsx                   # 404 page
│   └── providers.tsx                   # Client providers (React Query, etc.)
│
├── components/
│   ├── ui/                             # shadcn/ui components (auto-generated)
│   │   ├── button.tsx
│   │   ├── card.tsx
│   │   ├── input.tsx
│   │   ├── form.tsx
│   │   └── ...
│   ├── layout/                         # Layout components
│   │   ├── header.tsx
│   │   ├── footer.tsx
│   │   ├── sidebar.tsx
│   │   └── mobile-nav.tsx
│   ├── features/                       # Feature-specific components
│   │   ├── auth/
│   │   │   ├── login-form.tsx
│   │   │   └── register-form.tsx
│   │   ├── dashboard/
│   │   │   ├── stats-card.tsx
│   │   │   └── recent-activity.tsx
│   │   └── profile/
│   │       ├── profile-header.tsx
│   │       └── edit-profile-form.tsx
│   └── shared/                         # Shared/common components
│       ├── data-table.tsx
│       ├── search-bar.tsx
│       └── pagination.tsx
│
├── lib/                                # Utility functions & configurations
│   ├── api.ts                          # Axios instance + interceptors
│   ├── react-query.ts                  # React Query client config
│   ├── utils.ts                        # Utility functions (cn, formatters)
│   ├── validations.ts                  # Zod schemas
│   ├── constants.ts                    # App constants
│   └── auth.ts                         # Auth utilities (if using next-auth)
│
├── hooks/                              # Custom React hooks
│   ├── use-auth.ts                     # Authentication hook
│   ├── use-user.ts                     # User data hook (React Query)
│   ├── use-posts.ts                    # Posts data hook (React Query)
│   ├── use-media-query.ts              # Responsive design hook
│   └── use-toast.ts                    # Toast notifications (shadcn)
│
├── types/                              # TypeScript type definitions
│   ├── index.ts                        # Common types
│   ├── api.ts                          # API response types
│   ├── user.ts                         # User-related types
│   └── database.ts                     # Database types (Prisma generated)
│
├── actions/                            # Server Actions (Next.js 14+)
│   ├── auth.ts                         # Auth actions
│   ├── user.ts                         # User actions
│   └── posts.ts                        # Posts actions
│
├── config/                             # Configuration files
│   ├── site.ts                         # Site metadata (name, description, etc.)
│   └── navigation.ts                   # Navigation menu config
│
├── prisma/                             # Prisma ORM (if using database)
│   ├── schema.prisma                   # Database schema
│   └── migrations/                     # Database migrations
│
├── public/                             # Static assets
│   ├── images/
│   ├── icons/
│   └── fonts/
│
├── styles/                             # Global styles
│   └── globals.css                     # Tailwind imports + custom styles
│
├── .env.local                          # Environment variables (gitignored)
├── .env.example                        # Environment variables template
├── .eslintrc.json                      # ESLint config
├── .prettierrc                         # Prettier config
├── components.json                     # shadcn/ui config
├── next.config.js                      # Next.js config
├── tailwind.config.ts                  # Tailwind config
├── tsconfig.json                       # TypeScript config
├── package.json                        # Dependencies
└── README.md                           # Project documentation

Directory Purpose

app/
- Next.js 14 App Router pages and layouts. Use route groups
(name)
for logical grouping without affecting URLs.

components/
- All React components, organized by type:

  • ui/
    - shadcn/ui components (copy-paste, customizable)
  • layout/
    - Shared layout components (header, footer, sidebar)
  • features/
    - Feature-specific components (scoped to one feature)
  • shared/
    - Reusable components used across features

lib/
- Utility functions, configurations, and third-party library setups.

hooks/
- Custom React hooks, especially React Query hooks for API calls.

types/
- TypeScript type definitions and interfaces.

actions/
- Server Actions for form handling and server-side operations (Next.js 14+).

config/
- App configuration (site metadata, navigation menus, constants).

prisma/
- Database schema and migrations (if using Prisma).

public/
- Static files served at root URL.

styles/
- Global CSS (Tailwind imports + custom styles).

Workflow

Keep user informed at every step — this is a live build log.

⚠️ Important: All projects use PM2 for dev server management (port 3002 by default). This ensures:

  • Only one instance runs at a time (no port conflicts)
  • Easy process management (list/logs/restart/stop)
  • Persistent dev server across terminal sessions
  • Better error logging and debugging

Step 1: Project Setup

Ask:

  • Project name
  • Description/purpose
  • Optional features (chromium review, nginx preview)

Create Next.js project:

npx create-next-app@latest <project-name> \
  --typescript \
  --tailwind \
  --app \
  --no-src-dir \
  --import-alias "@/*"

→ Message user: "Next.js project initialized ✓"

Step 2: Create Directory Structure

Create all necessary directories following industry best practices:

cd <project-name>

# Create app route groups
mkdir -p app/\(auth\)/login app/\(auth\)/register
mkdir -p app/\(dashboard\)/dashboard app/\(dashboard\)/profile app/\(dashboard\)/settings
mkdir -p app/api/auth app/api/users

# Create component directories
mkdir -p components/ui components/layout components/features components/shared
mkdir -p components/features/auth components/features/dashboard components/features/profile

# Create utility directories
mkdir -p lib hooks types actions config

# Create static asset directories
mkdir -p public/images public/icons public/fonts

# Create styles directory
mkdir styles

# Create Prisma directory (if using database)
# mkdir -p prisma

Create essential config files:

config/site.ts
- Site metadata

export const siteConfig = {
  name: '<Project Name>',
  description: '<Project Description>',
  url: process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000',
  links: {
    github: 'https://github.com/...',
  },
};

config/navigation.ts
- Navigation menu

export const mainNav = [
  { title: 'Home', href: '/' },
  { title: 'Dashboard', href: '/dashboard' },
  { title: 'Profile', href: '/profile' },
];

export const dashboardNav = [
  { title: 'Overview', href: '/dashboard' },
  { title: 'Profile', href: '/profile' },
  { title: 'Settings', href: '/settings' },
];

.env.example
- Environment variables template

NEXT_PUBLIC_APP_URL=http://localhost:3000
NEXT_PUBLIC_API_BASE_URL=http://localhost:3000/api
DATABASE_URL=postgresql://...
NEXTAUTH_SECRET=...
NEXTAUTH_URL=http://localhost:3000

→ Message user: "Directory structure created ✓"

Step 3: Install Dependencies

Core dependencies:

cd <project-name>
npm install axios @tanstack/react-query
npm install -D @types/node

shadcn/ui setup (recommended):

npx shadcn-ui@latest init

This will prompt for configuration. Recommended answers:

  • Style: Default
  • Base color: Slate
  • CSS variables: Yes

Install essential shadcn components:

npx shadcn-ui@latest add button card input label select textarea
npx shadcn-ui@latest add dropdown-menu dialog sheet tabs
npx shadcn-ui@latest add table form avatar badge separator toast

Install form dependencies (for shadcn/ui forms):

npm install react-hook-form @hookform/resolvers zod

Optional (ask user based on needs):

npm install zustand  # State management
npm install next-auth  # Authentication
npm install prisma @prisma/client  # Database ORM

→ Message user: "Dependencies + shadcn/ui installed ✓"

Step 4: Configure Base Files

lib/api.ts
(axios instance)

import axios from 'axios';

export const api = axios.create({
  baseURL: process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3000/api',
  timeout: 10000,
  headers: { 'Content-Type': 'application/json' }
});

// Request interceptor (add auth tokens, etc.)
api.interceptors.request.use(
  (config) => {
    const token = localStorage.getItem('token');
    if (token) config.headers.Authorization = `Bearer ${token}`;
    return config;
  },
  (error) => Promise.reject(error)
);

// Response interceptor (handle errors globally)
api.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response?.status === 401) {
      // Handle unauthorized
    }
    return Promise.reject(error);
  }
);

lib/react-query.ts
(query client)

import { QueryClient } from '@tanstack/react-query';

export const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 60 * 1000, // 1 minute
      refetchOnWindowFocus: false,
      retry: 1,
    },
  },
});

app/providers.tsx
(wrap app with providers)

'use client';

import { QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { queryClient } from '@/lib/react-query';

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <QueryClientProvider client={queryClient}>
      {children}
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  );
}

Update

app/layout.tsx
to use Providers.

→ Message user: "Base configuration complete ✓"

Step 5: Generate Features

Ask what features/pages to build. For each feature:

  1. Create route (
    app/<feature>/page.tsx
    )
  2. Create components (
    components/features/<feature>/
    )
  3. Create API hooks (
    hooks/use<Feature>.ts
    ) using react-query
  4. Create types (
    types/<feature>.ts
    )
  5. Optionally create API routes (
    app/api/<feature>/route.ts
    )

Example: User Profile Feature

// types/user.ts
export interface User {
  id: string;
  name: string;
  email: string;
}

// hooks/useUser.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { api } from '@/lib/api';
import type { User } from '@/types/user';

export const useUser = (id: string) => {
  return useQuery({
    queryKey: ['user', id],
    queryFn: async () => {
      const { data } = await api.get<User>(`/users/${id}`);
      return data;
    },
  });
};

export const useUpdateUser = () => {
  const queryClient = useQueryClient();
  
  return useMutation({
    mutationFn: async (user: Partial<User>) => {
      const { data } = await api.patch<User>(`/users/${user.id}`, user);
      return data;
    },
    onSuccess: (data) => {
      queryClient.invalidateQueries({ queryKey: ['user', data.id] });
    },
  });
};

// app/profile/[id]/page.tsx
'use client';

import { useUser, useUpdateUser } from '@/hooks/useUser';

export default function ProfilePage({ params }: { params: { id: string } }) {
  const { data: user, isLoading, error } = useUser(params.id);
  const updateUser = useUpdateUser();

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <h1>{user?.name}</h1>
      <p>{user?.email}</p>
    </div>
  );
}

→ Message user after each feature: "Profile page complete ✓"

Step 6: Build UI with shadcn/ui Components

Use shadcn/ui components (already installed) for consistent, accessible UI. Apply Design Principles (see below).

Example: Profile page with shadcn/ui

import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';

export default function ProfilePage({ params }: { params: { id: string } }) {
  const { data: user, isLoading } = useUser(params.id);

  if (isLoading) return <Card className="w-full max-w-2xl mx-auto"><CardContent>Loading...</CardContent></Card>;

  return (
    <Card className="w-full max-w-2xl mx-auto">
      <CardHeader>
        <div className="flex items-center gap-4">
          <Avatar className="h-20 w-20">
            <AvatarImage src={user?.avatar} />
            <AvatarFallback>{user?.name[0]}</AvatarFallback>
          </Avatar>
          <div>
            <CardTitle>{user?.name}</CardTitle>
            <p className="text-sm text-muted-foreground">{user?.email}</p>
          </div>
        </div>
      </CardHeader>
      <CardContent>
        <Button>Edit Profile</Button>
      </CardContent>
    </Card>
  );
}

When to add more components:

  • Forms →
    npx shadcn-ui@latest add form input label
  • Data tables →
    npx shadcn-ui@latest add table
  • Navigation →
    npx shadcn-ui@latest add navigation-menu
  • Feedback →
    npx shadcn-ui@latest add toast alert

→ Message user: "UI built with shadcn/ui ✓"

Step 7: Visual Review (if chromium enabled)

Important: Use PM2 to manage the dev server (ensures only 1 instance runs, prevents port conflicts).

Start dev server with PM2:

# Stop any existing instance of this project
pm2 delete <project-name> 2>/dev/null || true

# Start with PM2 (port 3002 for nginx proxy)
PORT=3002 pm2 start npm --name "<project-name>" --cwd "$(pwd)" -- run dev

# Give PM2 a moment to start
sleep 2

Wait for server to be fully ready (critical - avoid white screen screenshots):

# Wait for "Ready in" message in PM2 logs (usually 5-15 seconds)
timeout=30
elapsed=0
while [ $elapsed -lt $timeout ]; do
  if pm2 logs <project-name> --nostream --lines 50 2>/dev/null | grep -q "Ready in"; then
    echo "Server ready!"
    sleep 3  # Extra buffer for module loading
    break
  fi
  sleep 1
  elapsed=$((elapsed + 1))
done

# Verify server is responding
if ! curl -s http://localhost:3002 > /dev/null; then
  echo "Warning: Server not responding on port 3002"
  pm2 logs <project-name> --nostream --lines 20
fi

Take screenshots (requires chromium):

bash scripts/screenshot.sh "http://localhost:3002" /tmp/review-desktop.png 1400 900
bash scripts/screenshot.sh "http://localhost:3002" /tmp/review-mobile.png 390 844

Review Checklist (analyze with

image
tool):

  • Desktop (1400px): Content centered, proper spacing
  • Mobile (390px):
    • No horizontal overflow (content fits within screen)
    • Text readable (not too small)
    • Padding appropriate (p-4 not p-24)
    • Touch targets large enough (min 44x44px)
    • No content cutting off edges

If issues found: Fix responsive classes, re-run screenshots.

Common fixes:

  • Large padding →
    p-4 md:p-8 lg:p-12
  • Large text →
    text-2xl md:text-4xl
  • Wide content → Add
    max-w-full
    or
    px-4

→ Message user: "Review complete, sending preview..."

Step 8: Environment Setup

Create

.env.local
:

NEXT_PUBLIC_API_BASE_URL=https://api.example.com
DATABASE_URL=postgresql://...
NEXTAUTH_SECRET=...

Create

.env.example
(template for user).

→ Message user: "Environment template created ✓"

Step 9: Scripts & Documentation

Update

package.json
scripts:

{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "type-check": "tsc --noEmit"
  }
}

Create

README.md
with:

  • Setup instructions
  • Environment variables needed
  • Development commands
  • API integration guide

→ Message user: "Documentation complete ✓"

Step 10: Export & Deploy Guidance

Stop PM2 dev server (if running):

pm2 delete <project-name> 2>/dev/null || true
pm2 save  # Persist PM2 process list

Zip the project:

cd .. && zip -r /tmp/<project-name>.zip <project-name>/

Send via message tool with

filePath
.

Provide deployment options:

  • Vercel (recommended):
    npx vercel
  • Netlify:
    npm run build && netlify deploy
  • Docker: Provide Dockerfile
  • Self-hosted: Provide systemd service + nginx config

→ Message user: "Project ready! 🚀"

Testing & Live Preview

Quick Test (during development)

1. PM2 dev server (always running after Step 7):

# Check status
pm2 list

# View logs
pm2 logs <project-name>

# Access locally
curl http://localhost:3002

2. Live preview URLs:

  • Local access:
    http://localhost:3002
  • Nginx proxy (if configured):
    http://<server-ip>:<external-port>
  • Mobile testing: Use nginx proxy or ngrok/tunneling service

3. Screenshot review (if chromium enabled):

# Desktop (1400x900)
bash scripts/screenshot.sh "http://localhost:3002" /tmp/desktop.png 1400 900

# Mobile (390x844)
bash scripts/screenshot.sh "http://localhost:3002" /tmp/mobile.png 390 844

End-to-End Testing Workflow

Full test sequence:

# 1. Check PM2 status
pm2 list | grep <project-name>

# 2. Verify dev server responding
curl -I http://localhost:3002

# 3. Take screenshots for visual verification
bash scripts/screenshot.sh "http://localhost:3002" /tmp/test-desktop.png 1400 900
bash scripts/screenshot.sh "http://localhost:3002" /tmp/test-mobile.png 390 844

# 4. Check logs for errors
pm2 logs <project-name> --lines 50 | grep -i error

# 5. Test API endpoints (if using API routes)
curl http://localhost:3002/api/health  # Example health check

# 6. Production build test
npm run build && npm run start  # Test production build

# 7. Type check
npm run type-check

Common Testing Scenarios

Scenario 1: Test responsive design

# Mobile, tablet, desktop
for width in 390 768 1400; do
  bash scripts/screenshot.sh "http://localhost:3002" /tmp/screen-${width}.png $width 900
done

Scenario 2: Test specific page/route

# Take screenshot of specific route
bash scripts/screenshot.sh "http://localhost:3002/dashboard" /tmp/dashboard.png 1400 900

Scenario 3: Test after making changes

# PM2 auto-reloads on file changes, verify in logs
pm2 logs <project-name> --lines 20

# Wait for "compiled successfully" then take new screenshot
bash scripts/screenshot.sh "http://localhost:3002" /tmp/updated.png 1400 900

Sharing Preview with User

Option 1: Screenshots

  • Send desktop + mobile screenshots via message tool
  • User provides feedback, you iterate

Option 2: Nginx proxy + external access

  • Set up nginx config (see Optional Features)
  • Share URL:
    http://<server-ip>:<port>
  • User can test live in browser

Option 3: Export & deploy

  • Zip project and send to user
  • User deploys to Vercel/Netlify
  • Test on production URL

API Integration Patterns

Pattern 1: REST API (default)

Use axios + react-query:

// hooks/usePosts.ts
import { useQuery, useMutation } from '@tanstack/react-query';
import { api } from '@/lib/api';

export const usePosts = () => {
  return useQuery({
    queryKey: ['posts'],
    queryFn: async () => {
      const { data } = await api.get('/posts');
      return data;
    },
  });
};

export const useCreatePost = () => {
  return useMutation({
    mutationFn: async (post: { title: string; body: string }) => {
      const { data } = await api.post('/posts', post);
      return data;
    },
  });
};

Pattern 2: GraphQL (optional)

Install:

npm install @apollo/client graphql

Setup Apollo Client, use

useQuery
and
useMutation
from Apollo.

Pattern 3: tRPC (optional)

For Next.js API routes with type safety:

npm install @trpc/server @trpc/client @trpc/react-query @trpc/next

Pattern 4: Server Actions (Next.js 14+)

For form handling without API routes:

// app/actions.ts
'use server';

export async function createPost(formData: FormData) {
  const title = formData.get('title');
  // ...
}

Always ask user which pattern they prefer for their use case.

Design Principles

Apply these consistently. These are quality standards.

Layout & Spacing

  • Consistent Tailwind spacing scale (4, 6, 8, 12, 16, 20, 24)
  • Max content width: max-w-5xl or max-w-6xl
  • Vertical rhythm: py-16 for sections, py-8 for subsections
  • Mobile: minimum px-4 padding

Typography

  • Clear hierarchy (h1 → h2 → h3, max 3-4 sizes)
  • Line length: max 65-75 characters (max-w-prose)
  • Font weight contrast (bold headings, regular body)
  • Text color hierarchy (slate-900 → slate-700 → slate-500)

Color & Contrast

  • WCAG AA minimum (4.5:1 contrast)
  • Limit palette (1 primary + 1 accent + neutrals)
  • Consistent accent usage (CTAs, links, active states)

Responsive Design (Critical)

  • Mobile-first (390px → 768px → 1024px) - Always design for 390px first
  • Responsive padding - Use Tailwind responsive classes:
    • Mobile:
      p-4
      or
      px-4 py-6
      (never p-24 on mobile!)
    • Tablet:
      md:p-8
      or
      md:px-6 md:py-8
    • Desktop:
      lg:p-12 xl:p-24
    • Example:
      <main className="p-4 md:p-8 lg:p-12">
  • Responsive text sizes - Scale down headings on mobile:
    • Mobile:
      text-2xl
      → Desktop:
      md:text-4xl
    • Mobile:
      text-lg
      → Desktop:
      md:text-2xl
  • No horizontal overflow - Content must fit within 390px width
    • Test: Check mobile screenshot for any content cutting off edges
    • Use
      max-w-full
      on containers
    • Break long words:
      break-words
  • Touch targets - min 44x44px for buttons/links on mobile
  • Stack on mobile - Grids collapse to single column:
    grid-cols-1 md:grid-cols-2 lg:grid-cols-3
  • Hamburger menu - Required on mobile for navigation

Components (Use shadcn/ui)

  • Icons: Use Lucide React (comes with shadcn/ui), never emoji
  • Buttons: Use
    <Button>
    component with variants (default, destructive, outline, ghost)
  • Forms: Use shadcn
    <Form>
    with react-hook-form integration
  • Cards: Use
    <Card>
    component for content sections
  • Dialogs/Modals: Use
    <Dialog>
    or
    <Sheet>
    components
  • Loading states: Use shadcn
    <Skeleton>
    component for loading UI
  • Error handling: Use
    <Alert>
    component for error messages
  • Data display: Use
    <Table>
    component for tabular data

shadcn/ui benefits: Accessible, customizable, copy-paste friendly, works with Tailwind

TypeScript Best Practices

  • Strict mode enabled
  • Explicit return types for functions
  • Interface over type for objects
  • Avoid
    any
    (use
    unknown
    if needed)
  • Use discriminated unions for variants

Performance

  • Use Next.js Image component (
    next/image
    )
  • Lazy load below-the-fold content
  • Code splitting (dynamic imports)
  • Memoize expensive computations (useMemo, useCallback)

Common Patterns

Form Handling (with shadcn/ui)

'use client';

import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import * as z from 'zod';
import { useMutation } from '@tanstack/react-query';
import { Button } from '@/components/ui/button';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { useToast } from '@/components/ui/use-toast';

const formSchema = z.object({
  name: z.string().min(2, 'Name must be at least 2 characters'),
  email: z.string().email('Invalid email address'),
});

export default function ContactForm() {
  const { toast } = useToast();
  const form = useForm<z.infer<typeof formSchema>>({
    resolver: zodResolver(formSchema),
    defaultValues: { name: '', email: '' },
  });

  const mutation = useMutation({
    mutationFn: async (data: z.infer<typeof formSchema>) => {
      const res = await api.post('/contact', data);
      return res.data;
    },
    onSuccess: () => {
      toast({ title: 'Success', description: 'Message sent!' });
      form.reset();
    },
    onError: (error) => {
      toast({ title: 'Error', description: error.message, variant: 'destructive' });
    },
  });

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit((data) => mutation.mutate(data))} className="space-y-4">
        <FormField
          control={form.control}
          name="name"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Name</FormLabel>
              <FormControl>
                <Input placeholder="John Doe" {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        <FormField
          control={form.control}
          name="email"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Email</FormLabel>
              <FormControl>
                <Input type="email" placeholder="john@example.com" {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        <Button type="submit" disabled={mutation.isPending}>
          {mutation.isPending ? 'Sending...' : 'Send Message'}
        </Button>
      </form>
    </Form>
  );
}

Note: Run

npx shadcn-ui@latest add form toast
and install
npm install react-hook-form @hookform/resolvers zod
for this pattern.

Pagination

const usePaginatedPosts = (page: number) => {
  return useQuery({
    queryKey: ['posts', page],
    queryFn: async () => {
      const { data } = await api.get(`/posts?page=${page}`);
      return data;
    },
    keepPreviousData: true, // Smooth transitions
  });
};

Infinite Scroll

import { useInfiniteQuery } from '@tanstack/react-query';

const useInfinitePosts = () => {
  return useInfiniteQuery({
    queryKey: ['posts'],
    queryFn: async ({ pageParam = 1 }) => {
      const { data } = await api.get(`/posts?page=${pageParam}`);
      return data;
    },
    getNextPageParam: (lastPage, pages) => lastPage.nextPage,
  });
};

Common Mistakes to Avoid

  • ❌ Not wrapping app with QueryClientProvider
  • ❌ Using axios without interceptors (no error handling)
  • ❌ Forgetting loading/error states in components
  • ❌ Not invalidating queries after mutations
  • ❌ Using
    any
    instead of proper TypeScript types
  • ❌ Client components when server components would work
  • ❌ Not using Next.js Image component (performance loss)
  • ❌ Missing error boundaries
  • ❌ Hardcoding API URLs (use env vars)
  • ❌ No mobile testing (always check responsive at 390px width)
  • Large padding on mobile (p-24 = 96px causes overflow on 390px screens)
  • Not using responsive Tailwind classes (use p-4 md:p-8 lg:p-12)
  • Horizontal overflow on mobile (content wider than 390px)
  • ❌ Building custom components when shadcn/ui has them (Button, Card, Dialog, etc.)
  • ❌ Using emoji for icons (use Lucide React icons from shadcn/ui)
  • ❌ Not installing
    @hookform/resolvers
    and
    zod
    before using shadcn forms
  • ❌ Forgetting to add
    <Toaster />
    component when using toast notifications
  • Taking screenshots before dev server is fully ready (causes white screens)
  • Not waiting for module loading (causes "Module not found" errors in screenshots)

Troubleshooting

White Screen Screenshots

Problem: Screenshots show blank white page Cause: Dev server not fully initialized before screenshot Solution:

  • Wait for "Ready in" message in dev server logs
  • Add 3-5 second buffer after "Ready" message
  • Verify localhost:3000 loads in browser before taking screenshot

Module Not Found Errors

Problem: React error "Module not found: Can't resolve @tanstack/react-query" Cause: Dev server started before all packages loaded Solution:

  • Restart dev server:
    pkill -f "next dev" && npm run dev
  • Verify packages in node_modules:
    ls node_modules/@tanstack/
  • Wait 10-15 seconds after
    npm install
    before starting dev server

Dev Server Won't Start

Problem: Port already in use (EADDRINUSE error) Solution (PM2 method):

# Check what's running
pm2 list

# Stop the conflicting process
pm2 delete <project-name>

# Or check port directly
lsof -ti:3002

# Kill process on port (if not PM2-managed)
kill -9 $(lsof -ti:3002)

# Restart with PM2
PORT=3002 pm2 start npm --name "<project-name>" --cwd "$(pwd)" -- run dev

PM2 Process Management

List all PM2 processes:

pm2 list

Check logs:

pm2 logs <project-name> --lines 50

Restart a process:

pm2 restart <project-name>

Stop a process:

pm2 stop <project-name>

Delete a process:

pm2 delete <project-name>

Ensure only one instance runs:

# Always delete before starting
pm2 delete <project-name> 2>/dev/null || true
PORT=3002 pm2 start npm --name "<project-name>" --cwd "$(pwd)" -- run dev

Common PM2 scenarios:

  1. Project won't start → Check logs:
    pm2 logs <project-name>
  2. Process keeps restarting → Module missing or port conflict, check logs
  3. Changes not reflecting → PM2 auto-reloads, verify in logs:
    pm2 logs <project-name> | grep compiled
  4. Multiple instances running → Delete all:
    pm2 delete all && pm2 list
  5. Check resource usage
    pm2 monit
    (real-time monitoring)
  6. Save PM2 process list
    pm2 save
    (persists across reboots)

Iteration & Updates

When user requests changes:

  1. Identify affected files
  2. Make changes
  3. PM2 auto-reloads (no manual restart needed for file changes)
  4. Run type check:
    npm run type-check
  5. Verify in logs:
    pm2 logs <project-name> --lines 20
  6. If chromium enabled: take new screenshot
  7. Report changes to user

Always explain what changed and why.


Quick Reference Cheat Sheet

Essential Commands

# Start dev server
pm2 delete <project-name> 2>/dev/null || true
PORT=3002 pm2 start npm --name "<project-name>" --cwd "$(pwd)" -- run dev

# Check status
pm2 list
pm2 logs <project-name>

# Take screenshots
bash scripts/screenshot.sh "http://localhost:3002" /tmp/desktop.png 1400 900
bash scripts/screenshot.sh "http://localhost:3002" /tmp/mobile.png 390 844

# Test production build
npm run build && npm run start

# Type check
npm run type-check

File Locations

  • Components:
    components/ui/
    (shadcn),
    components/features/
    (custom)
  • Pages:
    app/*/page.tsx
  • API routes:
    app/api/*/route.ts
  • Styles:
    app/globals.css
    ,
    tailwind.config.ts
  • Config:
    next.config.ts
    ,
    .env.local

Common shadcn Components

npx shadcn-ui@latest add button input form card table dialog toast

Live Preview URLs

  • Local: http://localhost:3002
  • Nginx proxy: http://<server-ip>:<external-port>
  • Mobile testing: Use nginx proxy or ngrok

Troubleshooting

  1. Port conflict
    pm2 delete <name>
    then restart
  2. White screen → Wait for "Ready in" message (check logs)
  3. Module errors
    npm install
    then restart PM2
  4. Type errors
    npm run type-check
  5. Layout breaks → Check responsive classes (p-4 md:p-8 lg:p-12)