| name | gif-component |
| description | Creates Next.js pages, layouts, and React components for the GIF project following App Router patterns: Server Components by default, 'use client' only when needed, design tokens from packages/ui, and role-based route structure for admin. Use when adding a page, layout, or component without a full API integration. Triggers on: '์ปดํฌ๋ํธ ๋ง๋ค์ด์ค', 'ํ์ด์ง ๋ง๋ค์ด์ค', '๋ ์ด์์ ์ถ๊ฐ', 'Server Component', 'Client Component', 'ํ๋ฉด ๋ง๋ค์ด์ค', 'UI ๊ตฌํ', 'page.tsx ๋ง๋ค์ด์ค', 'layout.tsx ์ถ๊ฐ', 'route ์ถ๊ฐ'. Does NOT trigger when also building an API layer โ use gif-feature for that. |
GIF Component Skill
The Core Decision: Server vs Client
Default to Server Component. Add "use client" only when the component needs:
useState, useReducer, or other React state hooks
useEffect or browser-only APIs
- Event handlers (
onClick, onChange, onSubmit, etc.)
- TanStack Query hooks (
useQuery, useMutation)
useRouter, usePathname, useSearchParams
If a page needs both server data and client interactivity: keep the page as a Server Component and extract only the interactive part into a separate Client Component child.
File Locations (FSD)
| What | FSD Layer | Path |
|---|
| Route entry | app | apps/{app}/src/app/{route}/page.tsx |
| Route layout / guard | app | apps/{app}/src/app/{route}/layout.tsx |
| Loading / Error UI | app | apps/{app}/src/app/{route}/loading.tsx / error.tsx |
| Page composition | views | apps/{app}/src/views/{page}/ui/{Page}View.tsx |
| Reusable page section | widgets | apps/{app}/src/widgets/{name}/ui/{Name}.tsx |
| User action UI | features | apps/{app}/src/features/{action}/ui/{Name}.tsx |
| Entity display UI | entities | apps/{app}/src/entities/{domain}/ui/{Name}.tsx |
| Shared primitive | shared | apps/{app}/src/shared/ui/{Name}.tsx |
| Cross-app shared | packages | packages/ui/src/{Name}.tsx + export in packages/ui/src/index.ts |
Which layer?
- Domain display (ProjectCard, TeamRow) โ
entities
- User action (SubmitForm, EvaluateButton) โ
features
- Composite section (table + pagination) โ
widgets
- Full assembled page โ
views
- Generic primitive (Spinner, Badge) โ
shared/ui or packages/ui
Server Component
import { SomeSharedComponent } from "@repo/ui";
export default async function ResourcePage() {
return (
<main className="p-6">
<h1 className="text-2xl font-semibold text-[var(--color-gray-900)]">
Page Title
</h1>
<SomeSharedComponent />
</main>
);
}
Client Component
"use client";
import { useState } from "react";
import { useResourceList } from "@/entities/{domain}/api/use{Domain}";
import { toast } from "sonner";
export function ResourceList() {
const { data, isPending, isError } = useResourceList();
const [selected, setSelected] = useState<number | null>(null);
if (isPending) return <p>Loading...</p>;
if (isError) return <p>Failed to load.</p>;
return (
<ul>
{data.map((item) => (
<li
key={item.id}
className="cursor-pointer hover:bg-[var(--color-gray-100)] p-2"
onClick={() => setSelected(item.id)}
>
{item.name}
</li>
))}
</ul>
);
}
Layout with Role Guard (Admin App)
import { redirect } from "next/navigation";
import { getSession } from "@/lib/auth";
export default async function CoordinatorLayout({
children,
}: {
children: React.ReactNode;
}) {
const session = await getSession();
if (!session || session.role !== "COORDINATOR") {
redirect("/unauthorized");
}
return <>{children}</>;
}
Admin Route Group Structure
apps/admin/src/app/
โโโ (coordinator)/ โ Idea Festival Coordinator
โ โโโ layout.tsx โ Role guard
โ โโโ dashboard/
โ โโโ page.tsx
โโโ (major)/ โ Major Subject Teacher
โโโ (general)/ โ General Subject Teacher
โโโ (head-of-grade)/ โ Head of Grade
Design Token Reference
Available CSS variables from packages/ui/src/tokens.css:
Colors: --color-black, --color-white
Gray: --color-gray-{900|800|700|600|500|400|300|200|100}
Yellow: --color-yellow-{900|800|...|50}
Orange: --color-orange-{900|800|...|50}
Shadows: --shadow-sm, --shadow-standard, --shadow-high, --shadow-new
Font: --font-pretendard
Use in Tailwind: bg-[var(--color-yellow-500)], text-[var(--color-gray-900)]
Error and Loading UI
"use client";
export default function Error({ reset }: { reset: () => void }) {
return (
<div>
<p>Something went wrong.</p>
<button onClick={reset}>Try again</button>
</div>
);
}
export default function Loading() {
return <p>Loading...</p>;
}