react-nextjs-app
Next.js App Router 프로젝트 컨벤션. Server/Client Components, metadata API, 특수 파일 패턴, Route Groups.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Next.js App Router 프로젝트 컨벤션. Server/Client Components, metadata API, 특수 파일 패턴, Route Groups.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
세션 핸드오프. 컨텍스트가 차기 전에 중요 맥락을 progress.md에 문서화하고 새 세션 킥오프 프롬프트를 클립보드에 복사한다. "핸드오프", "세션 정리하고 넘기자", "컨텍스트 정리", "이어갈 준비" 등의 요청에 사용.
MD 파일 또는 텍스트로 작업을 정의하면, 분석 → 디자인 확인 → 구현 → 검증 → 커밋 → PR까지 전체 플로우를 수행한다. "이거 만들어줘", "기능 구현해줘", "작업 시작하자", "이 티켓 진행해줘" 등 코드 작업 착수 발화에 사용.
Claude + OpenAI Codex 협업 스킬. MCP 서버 설정 시에만 사용 가능.
code-forge 상태 대시보드. REFLECT flag, quality 이벤트, notepad/decisions, usage 집계를 한 번에. forge-glow 같은 외부 도구는 --json으로 파싱.
교차 모델 토론. Agent Teams / Codex CLI / self-debate 모드 선택. 설계 결정, 아키텍처 선택 시 활용. "어느 쪽이 나을까", "설계 비교해줘", "토론시켜줘", "교차 검증해줘" 등의 요청에 사용.
화면 단위 E2E 테스트 자동화. Figma/코드 기반 테스트 케이스 도출 → Playwright 코드 생성 → Forge Loop(에스컬레이션 기반 자율 실행). "E2E 돌려줘", "화면 테스트 만들어줘", "이 페이지 자동 테스트" 등의 요청에 사용.
| name | react-nextjs-app |
| description | Next.js App Router 프로젝트 컨벤션. Server/Client Components, metadata API, 특수 파일 패턴, Route Groups. |
이 프로젝트는 Next.js App Router를 사용한다. app/ 디렉토리 기반의 파일 시스템 라우팅을 따른다.
app/
├── layout.tsx # 루트 레이아웃 (필수)
├── page.tsx # 루트 페이지
├── loading.tsx # 로딩 UI
├── error.tsx # 에러 UI
├── not-found.tsx # 404 UI
├── (auth)/ # Route Group (URL에 포함되지 않음)
│ ├── login/
│ │ └── page.tsx
│ └── register/
│ └── page.tsx
├── dashboard/
│ ├── layout.tsx # 중첩 레이아웃
│ ├── page.tsx
│ └── @analytics/ # Parallel Route
│ └── page.tsx
└── api/ # Route Handlers
└── users/
└── route.ts
// app/orders/page.tsx - 서버 컴포넌트 (기본값)
import { db } from '@/lib/db';
// async 함수 사용 가능
export default async function OrdersPage() {
const orders = await db.orders.findMany();
return (
<ul>
{orders.map((order) => (
<li key={order.id}>{order.name}</li>
))}
</ul>
);
}
// app/orders/OrderFilters.tsx
'use client'; // 반드시 파일 최상단에 선언
import { useState } from 'react';
export function OrderFilters() {
const [filter, setFilter] = useState('all');
return (
<select value={filter} onChange={(e) => setFilter(e.target.value)}>
<option value="all">전체</option>
<option value="pending">대기중</option>
</select>
);
}
// ✅ 좋은 예: Server Component가 Client Component를 children으로 받음
// app/orders/layout.tsx (Server Component)
import { OrderFilters } from './OrderFilters'; // Client Component
export default function OrdersLayout({ children }: { children: React.ReactNode }) {
return (
<div>
<OrderFilters />
{children}
</div>
);
}
// ❌ 나쁜 예: 불필요하게 'use client' 추가
'use client';
// useState, useEffect 없이 'use client' 선언
export default function StaticCard({ title }: { title: string }) {
return <div>{title}</div>;
}
// app/orders/page.tsx - 정적 메타데이터
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: '주문 목록',
description: '전체 주문 내역을 확인합니다.',
};
export default function OrdersPage() { ... }
// app/orders/[id]/page.tsx - 동적 메타데이터
import type { Metadata } from 'next';
interface Props {
params: { id: string };
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const order = await fetchOrder(params.id);
return {
title: `주문 #${order.id}`,
description: `${order.status} 상태의 주문입니다.`,
};
}
export default async function OrderDetailPage({ params }: Props) { ... }
// app/orders/loading.tsx
// Suspense 경계를 자동으로 생성
export default function OrdersLoading() {
return <div className="skeleton">로딩 중...</div>;
}
// app/orders/error.tsx
'use client'; // 에러 컴포넌트는 반드시 Client Component
import { useEffect } from 'react';
interface Props {
error: Error & { digest?: string };
reset: () => void;
}
export default function OrdersError({ error, reset }: Props) {
useEffect(() => {
console.error(error);
}, [error]);
return (
<div>
<p>주문을 불러오는 중 오류가 발생했습니다.</p>
<button onClick={reset}>다시 시도</button>
</div>
);
}
// app/dashboard/layout.tsx - 중첩 레이아웃
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div>
<nav>대시보드 네비게이션</nav>
<main>{children}</main>
</div>
);
}
// app/(marketing)/about/page.tsx
// URL: /about (그룹명 (marketing)은 URL에 포함되지 않음)
// app/(marketing)/layout.tsx - 마케팅 페이지 전용 레이아웃
export default function MarketingLayout({ children }: { children: React.ReactNode }) {
return <div className="marketing-layout">{children}</div>;
}
// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
analytics,
team,
}: {
children: React.ReactNode;
analytics: React.ReactNode; // @analytics 슬롯
team: React.ReactNode; // @team 슬롯
}) {
return (
<div>
{children}
{analytics}
{team}
</div>
);
}
// app/dashboard/@analytics/page.tsx - analytics 슬롯
export default function AnalyticsPage() {
return <div>분석 데이터</div>;
}
// app/api/orders/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const status = searchParams.get('status');
const orders = await fetchOrders(status);
return NextResponse.json(orders);
}
export async function POST(request: NextRequest) {
const body = await request.json();
const order = await createOrder(body);
return NextResponse.json(order, { status: 201 });
}
// ✅ Server Component에서 직접 fetch
export default async function Page() {
// Next.js가 자동으로 캐싱/중복 제거
const data = await fetch('https://api.example.com/data', {
next: { revalidate: 3600 }, // 1시간마다 재검증
});
const json = await data.json();
return <div>{json.title}</div>;
}
// ✅ 병렬 데이터 페칭
export default async function Page() {
const [orders, users] = await Promise.all([
fetchOrders(),
fetchUsers(),
]);
return <Dashboard orders={orders} users={users} />;
}