skills/nextjs/SKILL.md
Next.js 14+ App Router 패턴 가이드를 실행합니다.
npx skillsauth add excatt/superclaude-plusplus nextjsInstall 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.
Next.js 14+ App Router 패턴 가이드를 실행합니다.
app/
├── layout.tsx # 루트 레이아웃
├── page.tsx # 홈페이지
├── loading.tsx # 로딩 UI
├── error.tsx # 에러 UI
├── not-found.tsx # 404 UI
├── globals.css
├── (auth)/ # Route Group (URL 영향 X)
│ ├── login/page.tsx
│ └── register/page.tsx
├── dashboard/
│ ├── layout.tsx # 중첩 레이아웃
│ ├── page.tsx
│ └── [id]/ # 동적 라우트
│ └── page.tsx
├── api/ # API Routes
│ └── users/
│ └── route.ts
└── _components/ # Private folder
└── Header.tsx
// app/users/page.tsx
// 기본적으로 Server Component
async function getUsers() {
const res = await fetch('https://api.example.com/users', {
cache: 'force-cache', // 기본값: 정적
// cache: 'no-store', // 동적
// next: { revalidate: 60 }, // ISR
});
return res.json();
}
export default async function UsersPage() {
const users = await getUsers();
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
// app/_components/Counter.tsx
'use client';
import { useState } from 'react';
export function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
// app/actions.ts
'use server';
import { revalidatePath, revalidateTag } from 'next/cache';
import { redirect } from 'next/navigation';
import { z } from 'zod';
const schema = z.object({
title: z.string().min(1),
content: z.string().min(10),
});
export async function createPost(formData: FormData) {
const data = schema.parse({
title: formData.get('title'),
content: formData.get('content'),
});
await db.post.create({ data });
revalidatePath('/posts');
redirect('/posts');
}
// 폼에서 사용
// app/posts/new/page.tsx
export default function NewPost() {
return (
<form action={createPost}>
<input name="title" required />
<textarea name="content" required />
<button type="submit">Create</button>
</form>
);
}
'use client';
import { useFormState, useFormStatus } from 'react-dom';
import { createPost } from './actions';
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? 'Creating...' : 'Create'}
</button>
);
}
export function PostForm() {
const [state, action] = useFormState(createPost, { error: null });
return (
<form action={action}>
<input name="title" required />
<textarea name="content" required />
{state?.error && <p className="error">{state.error}</p>}
<SubmitButton />
</form>
);
}
// ✅ 병렬 fetch
async function Page() {
const [users, posts] = await Promise.all([
getUsers(),
getPosts(),
]);
return <div>...</div>;
}
// 의존성이 있는 경우
async function Page({ params }: { params: { id: string } }) {
const user = await getUser(params.id);
const posts = await getPostsByUser(user.id); // user 필요
return <div>...</div>;
}
// lib/data.ts
import { cache } from 'react';
export const getUser = cache(async (id: string) => {
const res = await fetch(`/api/users/${id}`);
return res.json();
});
export const preloadUser = (id: string) => {
void getUser(id);
};
// 사용
import { preloadUser } from '@/lib/data';
export default function UserLayout({ params, children }) {
preloadUser(params.id); // 미리 fetch 시작
return children;
}
// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const query = searchParams.get('q');
const users = await db.user.findMany({
where: query ? { name: { contains: query } } : undefined,
});
return NextResponse.json(users);
}
export async function POST(request: NextRequest) {
const body = await request.json();
const user = await db.user.create({ data: body });
return NextResponse.json(user, { status: 201 });
}
// app/api/users/[id]/route.ts
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const user = await db.user.findUnique({
where: { id: params.id },
});
if (!user) {
return NextResponse.json({ error: 'Not found' }, { status: 404 });
}
return NextResponse.json(user);
}
// app/layout.tsx
import { Metadata } from 'next';
export const metadata: Metadata = {
metadataBase: new URL('https://example.com'),
title: {
default: 'My Site',
template: '%s | My Site',
},
description: 'Site description',
openGraph: {
type: 'website',
locale: 'ko_KR',
siteName: 'My Site',
},
};
// app/posts/[slug]/page.tsx
export async function generateMetadata({ params }): Promise<Metadata> {
const post = await getPost(params.slug);
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [post.coverImage],
},
};
}
// app/dashboard/page.tsx
import { Suspense } from 'react';
export default function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
{/* 즉시 렌더링 */}
<StaticContent />
{/* 스트리밍 */}
<Suspense fallback={<ChartSkeleton />}>
<SlowChart />
</Suspense>
<Suspense fallback={<TableSkeleton />}>
<SlowTable />
</Suspense>
</div>
);
}
// loading.tsx 대신 세분화된 로딩 UI
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
// 인증 체크
const token = request.cookies.get('token');
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url));
}
// 헤더 추가
const response = NextResponse.next();
response.headers.set('x-custom-header', 'value');
return response;
}
export const config = {
matcher: ['/dashboard/:path*', '/api/:path*'],
};
// 정적 (기본)
fetch('https://api.example.com/data');
fetch('https://api.example.com/data', { cache: 'force-cache' });
// 동적
fetch('https://api.example.com/data', { cache: 'no-store' });
// ISR (시간 기반)
fetch('https://api.example.com/data', { next: { revalidate: 60 } });
// 태그 기반
fetch('https://api.example.com/posts', { next: { tags: ['posts'] } });
// 재검증
import { revalidatePath, revalidateTag } from 'next/cache';
revalidatePath('/posts'); // 경로 재검증
revalidateTag('posts'); // 태그 재검증
// app/posts/[slug]/page.tsx
export async function generateStaticParams() {
const posts = await getPosts();
return posts.map((post) => ({
slug: post.slug,
}));
}
// 동적 설정
export const dynamic = 'force-dynamic'; // 항상 동적
export const revalidate = 60; // ISR
// app/error.tsx
'use client';
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<div>
<h2>Something went wrong!</h2>
<button onClick={() => reset()}>Try again</button>
</div>
);
}
// app/global-error.tsx (루트 레이아웃 에러)
'use client';
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<html>
<body>
<h2>Something went wrong!</h2>
<button onClick={() => reset()}>Try again</button>
</body>
</html>
);
}
# .env.local
DATABASE_URL=... # 서버만
NEXT_PUBLIC_API_URL=... # 클라이언트 노출
# 사용
process.env.DATABASE_URL // 서버
process.env.NEXT_PUBLIC_API_URL // 클라이언트
## Next.js Implementation
### Route Structure
app/ ├── ...
### Data Fetching Strategy
| 페이지 | 방식 | 캐싱 |
|--------|------|------|
| Home | Static | ISR 60s |
| Dashboard | Dynamic | No cache |
### Server Actions
```typescript
// actions.ts
---
요청에 맞는 Next.js 구현을 설계하세요.
testing
사용자 계획을 기존 도메인 모델에 대해 stress-test하는 인터뷰 세션. 용어를 날카롭게 다듬고, 결정이 굳어질 때마다 CONTEXT.md(도메인 어휘 사전)와 ADR을 인라인으로 갱신한다. 새 기능 요구사항 탐색은 `/brainstorm`을, 기존 도메인 모델·용어와의 정합성 점검은 이 스킬을 사용한다.
development
# Excel (XLSX) Spreadsheet Skill Claude Code supports comprehensive spreadsheet operations through the **xlsx** skill, enabling creation, editing, and analysis of Excel files (.xlsx, .xlsm, .csv, .tsv). ## Trigger - When user needs Excel spreadsheet creation or editing - Financial modeling or data analysis required - Spreadsheet formulas and calculations needed - Data import from CSV/TSV files ## Core Capabilities **Primary functions include:** - Creating new spreadsheets with formulas and f
tools
Generate structured implementation workflows from PRDs and feature requirements
development
실시간 통신 설계 가이드를 실행합니다.