frontend-dev-guidelines
npx machina-cli add skill aiskillstore/marketplace/frontend-dev-guidelines --openclawFrontend 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
"use client"directive only if needed (interactivity, hooks, browser APIs) - 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
cn()utility for conditional classes - Default export at bottom
- Use Server Components for data fetching when possible
New Page Checklist
Creating a page? Set up this structure:
- Create
app/{route-name}/page.tsxfor route - Use Server Component by default
- Fetch data directly in Server Component
- Create
components/directory for page-specific components - Use
loading.tsxfor loading states - Use
error.tsxfor error boundaries - Export metadata for SEO
Import Aliases Quick Reference
| Alias | Resolves To | Example |
|---|---|---|
@/ | Project root | import { cn } from '@/lib/utils' |
@/components | components/ | import { Button } from '@/components/ui/button' |
@/lib | lib/ | import { cn } from '@/lib/utils' |
@/hooks | hooks/ | import { useMobile } from '@/hooks/use-mobile' |
@/app | app/ | import { Metadata } from 'next' |
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
"use client", can fetch data directly, smaller bundle - Client Components: Add
"use client"for interactivity, hooks, browser APIs
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
async/awaitin Server Components - No need for
useEffector data fetching libraries - Automatic request deduplication
Server Actions:
- Use for mutations (forms, updates)
- Create
app/actions/directory - Mark with
"use server"directive
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:
app/features/{feature}/: Feature-specific pages/routescomponents/: Truly reusable componentscomponents/ui/: Shadcn/ui components (don't modify directly)
🎨 Styling
Tailwind CSS + Shadcn/ui:
- Use Tailwind utility classes
- Use
cn()utility for conditional classes - 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
classNameprop or CSS variables
🛣️ 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
loading.tsxin route directory - Automatically wraps page in Suspense
- Use for route-level loading
Error Boundaries:
- Create
error.tsxin route directory - 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
next/imagefor images - Use
next/fontfor fonts - Lazy load Client Components when possible
- Use
useMemoanduseCallbackin Client Components - 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
anytype - 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
react-hook-formwithzodfor validation (Client Components) - 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
app/directory - Shadcn/ui Components: Use pre-built accessible components
- Tailwind CSS: Utility-first styling with
cn()helper - TypeScript Strict: No
any, explicit types - Performance: Use Server Components, optimize images, lazy load when needed
- File Organization: Features in
app/features/, shared incomponents/ - 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
Source
git clone https://github.com/aiskillstore/marketplace/blob/main/skills/0chan-smc/frontend-dev-guidelines/SKILL.mdView on GitHub Overview
Next.js 15 애플리케이션 개발을 위한 프런트엔드 가이드라인으로, 서버/클라이언트 컴포넌트, App Router 패턴, 파일 구조, Shadcn/ui 및 Tailwind CSS를 활용한 모던 패턴과 성능 최적화, TypeScript 모범 사례를 포함합니다. 컴포넌트, 페이지, 기능 생성, 데이터 페칭, 스타일링, 라우팅 등 프론트엔드 코드 작업에 필요한 실무 지침을 제공합니다.
How This Skill Works
기본은 Server Components를 권장하고, 필요한 경우에만 'use client'를 추가해 Client Components로 전환합니다. 데이터 페칭은 서버 컴포넌트에서 직접 처리하고, App Router 구조와 파일 배치를 통해 라우팅을 구성하며, Shadcn/ui와 Tailwind CSS를 사용해 UI를 구현합니다. 또한 TypeScript를 통해 명시적 Props 타입을 정의하고, tsconfig 경로 alias를 활용해 코드베이스를 일관되게 관리합니다.
When to Use It
- 새 컴포넌트나 페이지를 만들 때
- 새 기능을 구현할 때
- Server Components나 Server Actions를 통한 데이터 페칭이 필요할 때
- Next.js App Router로 라우팅을 설정할 때
- 성능 최적화, 스타일링( Tailwind, Shadcn/ui ), TypeScript 모범 사례를 적용할 때
Quick Start
- Step 1: Determine Server vs Client Component and add "use client" only if needed
- Step 2: Use TypeScript with explicit prop types; import Shadcn/ui components and Tailwind classes; use aliases
- Step 3: Create app/{route-name}/page.tsx (Server Component by default), add loading.tsx, error.tsx, and export SEO metadata
Best Practices
- 기본은 Server Components로 시작하고 필요 시에만 Client Components로 전환
- 인터랙티브가 필요한 경우에만 'use client'를 추가
- Shadcn/ui 컴포넌트와 Tailwind CSS를 일관되게 사용
- cn() 유틸리티로 조건부 클래스 관리
- Server Components에서 데이터 페칭을 우선하고 TypeScript 모범 사례를 준수
Example Use Cases
- App Router 기반의 새 페이지를 생성하고 page.tsx에서 서버 데이터 페칭을 구현
- 서버 컴포넌트로 데이터를 불러오고 필요 시 Client Component로 인터랙티브한 UI를 연결
- Shadcn/ui Card, Button 등 컴포넌트를 활용해 UI 구성
- loading.tsx와 error.tsx를 사용해 로딩 및 에러 경계 처리
- Props를 명시적으로 정의하고 타입스크립트로 컴포넌트 재사용성을 높임