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.
git clone https://github.com/diegosouzapw/awesome-omni-skill
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"
skills/development/ui-development/SKILL.mdUI 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:
- 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:
(Debian/Ubuntu)sudo apt-get install chromium-browser - 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
(PM2 port)http://localhost:3002
Before starting, ask user if they want to enable optional features.
Common Project Types
Quick reference for typical requests:
- Dashboard/Admin Panel → Use
route group, shadcn data tables, charts(dashboard) - Landing Page → Single
, hero section, features grid, testimonialsapp/page.tsx - Todo/Task App → shadcn checkbox, input, button; local state or API
- Blog/CMS → Dynamic routes
, markdown supportapp/blog/[slug]/page.tsx - E-commerce → Product catalog, cart state (Zustand), checkout flow
- SaaS App → Auth (
group), protected routes, subscription logic(auth) - 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
- Next.js 14 App Router pages and layouts. Use route groups app/
(name) for logical grouping without affecting URLs.
- All React components, organized by type:components/
- shadcn/ui components (copy-paste, customizable)ui/
- Shared layout components (header, footer, sidebar)layout/
- Feature-specific components (scoped to one feature)features/
- Reusable components used across featuresshared/
- Utility functions, configurations, and third-party library setups.lib/
- Custom React hooks, especially React Query hooks for API calls.hooks/
- TypeScript type definitions and interfaces.types/
- Server Actions for form handling and server-side operations (Next.js 14+).actions/
- App configuration (site metadata, navigation menus, constants).config/
- Database schema and migrations (if using Prisma).prisma/
- Static files served at root URL.public/
- Global CSS (Tailwind imports + custom styles).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:
- Site metadataconfig/site.ts
export const siteConfig = { name: '<Project Name>', description: '<Project Description>', url: process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000', links: { github: 'https://github.com/...', }, };
- Navigation menuconfig/navigation.ts
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' }, ];
- Environment variables template.env.example
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)
lib/api.tsimport 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)
lib/react-query.tsimport { 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)
app/providers.tsx'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:
- Create route (
)app/<feature>/page.tsx - Create components (
)components/features/<feature>/ - Create API hooks (
) using react-queryhooks/use<Feature>.ts - Create types (
)types/<feature>.ts - 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
ormax-w-fullpx-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:
orp-4
(never p-24 on mobile!)px-4 py-6 - Tablet:
ormd:p-8md:px-6 md:py-8 - Desktop:
lg:p-12 xl:p-24 - Example:
<main className="p-4 md:p-8 lg:p-12">
- Mobile:
- Responsive text sizes - Scale down headings on mobile:
- Mobile:
→ Desktop:text-2xlmd:text-4xl - Mobile:
→ Desktop:text-lgmd:text-2xl
- Mobile:
- No horizontal overflow - Content must fit within 390px width
- Test: Check mobile screenshot for any content cutting off edges
- Use
on containersmax-w-full - 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
component with variants (default, destructive, outline, ghost)<Button> - Forms: Use shadcn
with react-hook-form integration<Form> - Cards: Use
component for content sections<Card> - Dialogs/Modals: Use
or<Dialog>
components<Sheet> - Loading states: Use shadcn
component for loading UI<Skeleton> - Error handling: Use
component for error messages<Alert> - Data display: Use
component for tabular data<Table>
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
(useany
if needed)unknown - 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
instead of proper TypeScript typesany - ❌ 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
and@hookform/resolvers
before using shadcn formszod - ❌ Forgetting to add
component when using toast notifications<Toaster /> - ❌ 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
before starting dev servernpm install
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:
- Project won't start → Check logs:
pm2 logs <project-name> - Process keeps restarting → Module missing or port conflict, check logs
- Changes not reflecting → PM2 auto-reloads, verify in logs:
pm2 logs <project-name> | grep compiled - Multiple instances running → Delete all:
pm2 delete all && pm2 list - Check resource usage →
(real-time monitoring)pm2 monit - Save PM2 process list →
(persists across reboots)pm2 save
Iteration & Updates
When user requests changes:
- Identify affected files
- Make changes
- PM2 auto-reloads (no manual restart needed for file changes)
- Run type check:
npm run type-check - Verify in logs:
pm2 logs <project-name> --lines 20 - If chromium enabled: take new screenshot
- 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:
(shadcn),components/ui/
(custom)components/features/ - Pages:
app/*/page.tsx - API routes:
app/api/*/route.ts - Styles:
,app/globals.csstailwind.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
- Port conflict →
then restartpm2 delete <name> - White screen → Wait for "Ready in" message (check logs)
- Module errors →
then restart PM2npm install - Type errors →
npm run type-check - Layout breaks → Check responsive classes (p-4 md:p-8 lg:p-12)