home / skills / codingheader / myskills / 0chan-smc-frontend-dev-guidelines

This skill guides modern Next.js 15 frontend development with React 19, TS, Tailwind, and Server/Client patterns for scalable apps.

npx playbooks add skill codingheader/myskills --skill 0chan-smc-frontend-dev-guidelines

Review the files below or copy the command above to add this skill to your agents.

Files (2)
SKILL.md
15.1 KB
---
name: frontend-dev-guidelines
description: Next.js 15 애플리케이션을 위한 프론트엔드 개발 가이드라인. React 19, TypeScript, Shadcn/ui, Tailwind CSS를 사용한 모던 패턴. Server Components, Client Components, App Router, 파일 구조, Shadcn/ui 컴포넌트, 성능 최적화, TypeScript 모범 사례 포함. 컴포넌트, 페이지, 기능 생성, 데이터 페칭, 스타일링, 라우팅, 프론트엔드 코드 작업 시 사용.
---

# Frontend 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.tsx` for route
- [ ] Use Server Component by default
- [ ] Fetch data directly in Server Component
- [ ] Create `components/` directory for page-specific components
- [ ] Use `loading.tsx` for loading states
- [ ] Use `error.tsx` for 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

```typescript
// 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:**

```typescript
// 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:**

```typescript
// 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/await` in Server Components
- No need for `useEffect` or 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:**

```typescript
// 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:**

```typescript
// 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/routes
- `components/`: Truly reusable components
- `components/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:**

```typescript
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 `className` prop 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:**

```typescript
// 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:**

```typescript
// 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.tsx` in route directory
- Automatically wraps page in Suspense
- Use for route-level loading

**Error Boundaries:**

- Create `error.tsx` in route directory
- Automatically catches errors in route
- Can reset error state

**Example Loading UI:**

```typescript
// 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:**

```typescript
// 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/image` for images
- Use `next/font` for fonts
- Lazy load Client Components when possible
- Use `useMemo` and `useCallback` in Client Components
- Stream data with Suspense boundaries

**Image Optimization:**

```typescript
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:**

```typescript
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 `any` type
- Explicit return types on functions
- Type imports: `import type { Post } from '@/types/post'`
- Component prop interfaces with JSDoc

**Example:**

```typescript
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-form` with `zod` for validation (Client Components)
- Use Shadcn/ui Form components

**Example Form with Server Action:**

```typescript
// 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:**

```typescript
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

1. **Server Components First**: Default to Server Components, use Client Components only when needed
2. **App Router Structure**: Use file-based routing with `app/` directory
3. **Shadcn/ui Components**: Use pre-built accessible components
4. **Tailwind CSS**: Utility-first styling with `cn()` helper
5. **TypeScript Strict**: No `any`, explicit types
6. **Performance**: Use Server Components, optimize images, lazy load when needed
7. **File Organization**: Features in `app/features/`, shared in `components/`
8. **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:**

```typescript
// 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:**

```typescript
// 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

Overview

This skill provides concise frontend development guidelines for Next.js 15 with React 19, TypeScript, Shadcn/ui and Tailwind CSS. It focuses on modern patterns: Server vs Client Components, App Router conventions, file organization, performance, and TypeScript best practices. Use it to standardize component and page creation, data fetching, routing, and styling in a Next.js app.

How this skill works

The guide prescribes default Server Components for data fetching and smaller bundles, and Client Components only for interactivity (with a 'use client' directive). It defines file and route layouts under app/, import aliases (e.g. @/components, @/lib), and common imports. It also describes Server Actions for mutations, Shadcn/ui usage, Tailwind styling via a cn() utility, and TypeScript rules for strict typing.

When to use it

  • When creating new components or pages to follow a consistent pattern
  • When implementing data fetching or Server Actions in the App Router
  • When setting up routing, nested routes, or dynamic routes under app/
  • When styling with Tailwind and customizing Shadcn/ui components
  • When optimizing performance (images, fonts, streaming, lazy loading)

Best practices

  • Default to Server Components; add 'use client' only for hooks or browser APIs
  • Use TypeScript strict mode, avoid any, and declare explicit prop types
  • Organize code by feature: app/features/{feature}/ with components and actions
  • Import shared UI from @/components/ui and utilities from @/lib using aliases
  • Use next/image and next/font, lazy-load client logic and memoize in client components

Example use cases

  • Create a posts list page: app/posts/page.tsx fetches data server-side and renders a PostList Server Component
  • Build an interactive form: a Client Component PostForm uses react-hook-form and calls a Server Action in app/actions/
  • Add a dynamic route: app/posts/[id]/page.tsx fetches a single post and calls notFound() if missing
  • Add route-level UX: include loading.tsx and error.tsx in a route folder for Suspense and error boundaries
  • Style reusable buttons with cn() and extend Shadcn/ui components via className

FAQ

When should I choose a Client Component?

Choose a Client Component only if you need state, effects, browser-only APIs, or third-party client-only libraries. Keep everything else as Server Components.

How do I handle form submissions securely?

Use Server Actions ('use server') for form mutations, validate input with zod, and redirect from the server after success.