skills/0chan-smc/frontend-dev-guidelines/SKILL.md
Next.js 15 애플리케이션을 위한 프론트엔드 개발 가이드라인. React 19, TypeScript, Shadcn/ui, Tailwind CSS를 사용한 모던 패턴. Server Components, Client Components, App Router, 파일 구조, Shadcn/ui 컴포넌트, 성능 최적화, TypeScript 모범 사례 포함. 컴포넌트, 페이지, 기능 생성, 데이터 페칭, 스타일링, 라우팅, 프론트엔드 코드 작업 시 사용.
npx skillsauth add aiskillstore/marketplace frontend-dev-guidelinesInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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.
Creating a component? Follow this checklist:
"use client" directive only if needed (interactivity, hooks, browser APIs)@/components/ui@/components, @/lib, @/hookscn() utility for conditional classesCreating a page? Set up this structure:
app/{route-name}/page.tsx for routecomponents/ directory for page-specific componentsloading.tsx for loading stateserror.tsx for error boundaries| 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
// 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'
Server Components vs Client Components:
"use client", can fetch data directly, smaller bundle"use client" for interactivity, hooks, browser APIsKey Concepts:
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>
)
}
PRIMARY PATTERN: Server Components
async/await in Server ComponentsuseEffect or data fetching librariesServer Actions:
app/actions/ directory"use server" directiveExample 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')
}
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)Tailwind CSS + Shadcn/ui:
cn() utility for conditional classesapp/globals.cssStyling 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:
@/components/ui/{component-name}className prop or CSS variablesNext.js App Router - File-Based:
app/{route-name}/page.tsxapp/{parent}/{child}/page.tsxapp/posts/[id]/page.tsxapp/(marketing)/about/page.tsxExample 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 States:
loading.tsx in route directoryError Boundaries:
error.tsx in route directoryExample 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>
)
}
Optimization Patterns:
next/image for imagesnext/font for fontsuseMemo and useCallback in Client ComponentsImage 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>
)
}
Standards:
any typeimport type { Post } from '@/types/post'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>
)
}
Form Handling:
react-hook-form with zod for validation (Client 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',
},
}
app/ directorycn() helperany, explicit typesapp/features/, shared in components/@/ prefix for clean importsapp/
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
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>
)
}
Skill Status: Optimized for Next.js 15 with App Router, Server Components, and Shadcn/ui
development
Apple Human Interface Guidelines for content display components. Use this skill when the user asks about charts component, collection view, image view, web view, color well, image well, activity view, lockup, data visualization, content display, displaying images, rendering web content, color pickers, or presenting collections of items in Apple apps. Also use when the user says how should I display charts, what's the best way to show images, should I use a web view, how do I build a grid of items, what component shows media, or how do I present a share sheet. Cross-references: hig-foundations for color/typography/accessibility, hig-patterns for data visualization patterns, hig-components-layout for structural containers, hig-platforms for platform-specific component behavior.
tools
Automate HelpDesk tasks via Rube MCP (Composio): list tickets, manage views, use canned responses, and configure custom fields. Always search tools first for current schemas.
testing
Expert Haskell engineer specializing in advanced type systems, pure functional design, and high-reliability software. Use PROACTIVELY for type-level programming, concurrency, and architecture guidance.
tools
GraphQL gives clients exactly the data they need - no more, no less. One endpoint, typed schema, introspection. But the flexibility that makes it powerful also makes it dangerous. Without proper controls, clients can craft queries that bring down your server. This skill covers schema design, resolvers, DataLoader for N+1 prevention, federation for microservices, and client integration with Apollo/urql. Key insight: GraphQL is a contract. The schema is the API documentation. Design it carefully.