Marketplace frontend-dev-guidelines
Next.js 15 애플리케이션을 위한 프론트엔드 개발 가이드라인. React 19, TypeScript, Shadcn/ui, Tailwind CSS를 사용한 모던 패턴. Server Components, Client Components, App Router, 파일 구조, Shadcn/ui 컴포넌트, 성능 최적화, TypeScript 모범 사례 포함. 컴포넌트, 페이지, 기능 생성, 데이터 페칭, 스타일링, 라우팅, 프론트엔드 코드 작업 시 사용.
git clone https://github.com/aiskillstore/marketplace
T=$(mktemp -d) && git clone --depth=1 https://github.com/aiskillstore/marketplace "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/0chan-smc/frontend-dev-guidelines" ~/.claude/skills/aiskillstore-marketplace-frontend-dev-guidelines && rm -rf "$T"
skills/0chan-smc/frontend-dev-guidelines/SKILL.mdFrontend Development Guidelines
Purpose
Comprehensive guide for modern Next.js 15 development with React 19, emphasizing Server Components, Client Components, App Router patterns, Shadcn/ui components, proper file organization, and performance optimization.
When to Use This Skill
- Creating new components or pages
- Building new features
- Fetching data (Server Components, Server Actions)
- Setting up routing with Next.js App Router
- Styling components with Tailwind CSS and Shadcn/ui
- Performance optimization
- Organizing frontend code
- TypeScript best practices
Quick Start
New Component Checklist
Creating a component? Follow this checklist:
- Determine Server vs Client Component (default: Server Component)
- Add
directive only if needed (interactivity, hooks, browser APIs)"use client" - Use TypeScript with explicit prop types
- Import Shadcn/ui components from
@/components/ui - Use Tailwind CSS classes for styling
- Import aliases:
,@/components
,@/lib@/hooks - Use
utility for conditional classescn() - Default export at bottom
- Use Server Components for data fetching when possible
New Page Checklist
Creating a page? Set up this structure:
- Create
for routeapp/{route-name}/page.tsx - Use Server Component by default
- Fetch data directly in Server Component
- Create
directory for page-specific componentscomponents/ - Use
for loading statesloading.tsx - Use
for error boundarieserror.tsx - Export metadata for SEO
Import Aliases Quick Reference
| Alias | Resolves To | Example |
|---|---|---|
| Project root | |
| | |
| | |
| | |
| | |
Defined in:
tsconfig.json paths configuration
Common Imports Cheatsheet
// Next.js import { Metadata } from 'next' import { Suspense } from 'react' import { notFound, redirect } from 'next/navigation' // React (Client Components only) ;('use client') import { useState, useCallback, useMemo } from 'react' // Shadcn/ui Components import { Button } from '@/components/ui/button' import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card' import { Input } from '@/components/ui/input' // Utilities import { cn } from '@/lib/utils' // Hooks (Client Components only) import { useMobile } from '@/hooks/use-mobile' // Types import type { ComponentProps } from 'react'
Topic Guides
🎨 Component Patterns
Server Components vs Client Components:
- Server Components (default): No
, can fetch data directly, smaller bundle"use client" - Client Components: Add
for interactivity, hooks, browser APIs"use client"
Key Concepts:
- Default to Server Components
- Only use Client Components when necessary
- Use Shadcn/ui components (already Client Components)
- Component structure: Props → Data Fetching → Render → Export
Example Server Component:
// app/features/posts/components/PostList.tsx import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card' interface PostListProps { posts: Post[] } export function PostList({ posts }: PostListProps) { return ( <div className='grid gap-4'> {posts.map((post) => ( <Card key={post.id}> <CardHeader> <CardTitle>{post.title}</CardTitle> </CardHeader> <CardContent>{post.content}</CardContent> </Card> ))} </div> ) }
Example Client Component:
// app/features/posts/components/PostForm.tsx 'use client' import { useState } from 'react' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' export function PostForm() { const [title, setTitle] = useState('') return ( <form> <Input value={title} onChange={(e) => setTitle(e.target.value)} /> <Button type='submit'>Submit</Button> </form> ) }
📊 Data Fetching
PRIMARY PATTERN: Server Components
- Fetch data directly in Server Components
- Use
in Server Componentsasync/await - No need for
or data fetching librariesuseEffect - Automatic request deduplication
Server Actions:
- Use for mutations (forms, updates)
- Create
directoryapp/actions/ - Mark with
directive"use server"
Example Server Component with Data Fetching:
// app/posts/page.tsx import { PostList } from '@/components/PostList' async function getPosts() { const res = await fetch('https://api.example.com/posts', { cache: 'no-store', // or 'force-cache', 'revalidate' }) return res.json() } export default async function PostsPage() { const posts = await getPosts() return <PostList posts={posts} /> }
Example Server Action:
// app/actions/posts.ts 'use server' export async function createPost(formData: FormData) { const title = formData.get('title') // ... validation and creation logic redirect('/posts') }
📁 File Organization
App Router Structure:
app/ (routes)/ page.tsx # Route page layout.tsx # Route layout loading.tsx # Loading UI error.tsx # Error UI components/ # Shared components ui/ # Shadcn/ui components features/ # Feature-specific code posts/ components/ # Feature components actions/ # Server Actions types/ # TypeScript types lib/ utils.ts # Utilities (cn, etc.) hooks/ use-mobile.ts # Custom hooks (Client only)
Feature Organization:
: Feature-specific pages/routesapp/features/{feature}/
: Truly reusable componentscomponents/
: Shadcn/ui components (don't modify directly)components/ui/
🎨 Styling
Tailwind CSS + Shadcn/ui:
- Use Tailwind utility classes
- Use
utility for conditional classescn() - Shadcn/ui components use CSS variables for theming
- Customize theme in
app/globals.css
Styling Patterns:
import { cn } from '@/lib/utils' interface ButtonProps { variant?: 'primary' | 'secondary' className?: string } export function Button({ variant = 'primary', className }: ButtonProps) { return ( <button className={cn( 'rounded-md px-4 py-2', variant === 'primary' && 'bg-primary text-primary-foreground', variant === 'secondary' && 'bg-secondary text-secondary-foreground', className, )} > Click me </button> ) }
Shadcn/ui Components:
- Import from
@/components/ui/{component-name} - Components are already styled and accessible
- Customize via
prop or CSS variablesclassName
🛣️ Routing
Next.js App Router - File-Based:
- Directory:
app/{route-name}/page.tsx - Nested routes:
app/{parent}/{child}/page.tsx - Dynamic routes:
app/posts/[id]/page.tsx - Route groups:
app/(marketing)/about/page.tsx
Example Route:
// app/posts/page.tsx import { Metadata } from 'next' import { PostList } from '@/components/PostList' export const metadata: Metadata = { title: 'Posts', description: 'List of all posts', } export default async function PostsPage() { const posts = await getPosts() return ( <div className='container mx-auto py-8'> <h1 className='text-3xl font-bold mb-6'>Posts</h1> <PostList posts={posts} /> </div> ) }
Dynamic Route:
// app/posts/[id]/page.tsx interface PostPageProps { params: Promise<{ id: string }> } export default async function PostPage({ params }: PostPageProps) { const { id } = await params const post = await getPost(id) if (!post) { notFound() } return <PostDetail post={post} /> }
⏳ Loading & Error States
Loading States:
- Create
in route directoryloading.tsx - Automatically wraps page in Suspense
- Use for route-level loading
Error Boundaries:
- Create
in route directoryerror.tsx - Automatically catches errors in route
- Can reset error state
Example Loading UI:
// app/posts/loading.tsx export default function Loading() { return ( <div className='flex items-center justify-center min-h-screen'> <div className='animate-spin rounded-full h-8 w-8 border-b-2 border-primary' /> </div> ) }
Example Error UI:
// app/posts/error.tsx 'use client' import { useEffect } from 'react' import { Button } from '@/components/ui/button' export default function Error({ error, reset, }: { error: Error & { digest?: string } reset: () => void }) { useEffect(() => { console.error(error) }, [error]) return ( <div className='flex flex-col items-center justify-center min-h-screen'> <h2 className='text-2xl font-bold mb-4'>Something went wrong!</h2> <Button onClick={reset}>Try again</Button> </div> ) }
⚡ Performance
Optimization Patterns:
- Use Server Components (smaller bundle)
- Use
for imagesnext/image - Use
for fontsnext/font - Lazy load Client Components when possible
- Use
anduseMemo
in Client ComponentsuseCallback - Stream data with Suspense boundaries
Image Optimization:
import Image from 'next/image' export function Avatar({ src, alt }: { src: string; alt: string }) { return ( <Image src={src} alt={alt} width={40} height={40} className='rounded-full' /> ) }
Streaming with Suspense:
import { Suspense } from 'react' import { PostList } from '@/components/PostList' import { Loading } from '@/components/Loading' export default function Page() { return ( <div> <Suspense fallback={<Loading />}> <PostList /> </Suspense> </div> ) }
📘 TypeScript
Standards:
- Strict mode enabled
- No
typeany - Explicit return types on functions
- Type imports:
import type { Post } from '@/types/post' - Component prop interfaces with JSDoc
Example:
import type { ComponentProps } from 'react' import { Button } from '@/components/ui/button' /** * Custom button component with loading state */ interface CustomButtonProps extends ComponentProps<typeof Button> { isLoading?: boolean } export function CustomButton({ isLoading, children, ...props }: CustomButtonProps) { return ( <Button disabled={isLoading} {...props}> {isLoading ? 'Loading...' : children} </Button> ) }
🔧 Common Patterns
Form Handling:
- Use Server Actions for form submissions
- Use
withreact-hook-form
for validation (Client Components)zod - Use Shadcn/ui Form components
Example Form with Server Action:
// app/actions/posts.ts 'use server' import { z } from 'zod' const createPostSchema = z.object({ title: z.string().min(1), content: z.string().min(1), }) export async function createPost(formData: FormData) { const rawData = { title: formData.get('title'), content: formData.get('content'), } const validated = createPostSchema.parse(rawData) // ... create post logic redirect('/posts') }
Metadata:
import { Metadata } from 'next' export const metadata: Metadata = { title: 'Posts', description: 'List of all posts', openGraph: { title: 'Posts', description: 'List of all posts', }, }
Core Principles
- Server Components First: Default to Server Components, use Client Components only when needed
- App Router Structure: Use file-based routing with
directoryapp/ - Shadcn/ui Components: Use pre-built accessible components
- Tailwind CSS: Utility-first styling with
helpercn() - TypeScript Strict: No
, explicit typesany - Performance: Use Server Components, optimize images, lazy load when needed
- File Organization: Features in
, shared inapp/features/components/ - Import Aliases: Use
prefix for clean imports@/
Quick Reference: File Structure
app/ layout.tsx # Root layout page.tsx # Home page globals.css # Global styles (routes)/ posts/ page.tsx # Posts list page [id]/ page.tsx # Post detail page loading.tsx # Loading UI error.tsx # Error UI features/ posts/ components/ PostList.tsx # Feature components actions/ posts.ts # Server Actions components/ ui/ # Shadcn/ui components button.tsx card.tsx lib/ utils.ts # Utilities (cn, etc.) hooks/ use-mobile.ts # Custom hooks
Modern Component Template (Quick Copy)
Server Component:
// app/components/PostCard.tsx import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card' import type { Post } from '@/types/post' interface PostCardProps { post: Post } export function PostCard({ post }: PostCardProps) { return ( <Card> <CardHeader> <CardTitle>{post.title}</CardTitle> </CardHeader> <CardContent> <p>{post.content}</p> </CardContent> </Card> ) }
Client Component:
// app/components/PostForm.tsx 'use client' import { useState } from 'react' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { createPost } from '@/app/actions/posts' import { cn } from '@/lib/utils' export function PostForm({ className }: { className?: string }) { const [isLoading, setIsLoading] = useState(false) async function handleSubmit(formData: FormData) { setIsLoading(true) await createPost(formData) setIsLoading(false) } return ( <form action={handleSubmit} className={cn('space-y-4', className)}> <Input name='title' placeholder='Post title' required /> <Input name='content' placeholder='Post content' required /> <Button type='submit' disabled={isLoading}> {isLoading ? 'Creating...' : 'Create Post'} </Button> </form> ) }
Related Skills
- backend-dev-guidelines: Backend API patterns that frontend consumes
Skill Status: Optimized for Next.js 15 with App Router, Server Components, and Shadcn/ui