본문으로 건너뛰기
cccskills
무료GitHub에 공개

frontend-component-builder

Build pages and components in the Next.js 15 App Router (RSC by default, "use client" only when needed), Tailwind for styling, typed props, and the existing CartProvider for cart state. Use when creating or modifying pages, layouts, components, or forms in frontend/.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

연동 도구Supabase
설치 방법 보기

포함된 파일(1)

  • SKILL.md9.2 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

Component Builder Skill

Version: 1.0 | Working Directory: frontend/

Build pages and components for $ARGUMENTS following the App Router conventions used in this repo.

Overview

This skill provides patterns for:

  • Page Layer — app/(store)/... and app/admin/... routes (Server Components by default)
  • Component Layer — reusable Server or Client components in components/
  • State — local React state, plus the Cart Context in components/CartProvider.tsx
  • Styling — Tailwind utilities

This MVP intentionally has no Redux, no React Query, no Formik, no Zod. Don't add them.

Constitution (Non-Negotiable Rules)

#RuleWhy
1Server Component by default; add "use client" only when the file needs hooks (useState, useEffect, context), event handlers, or browser-only APIsRSC keeps bundles small and fetches data on the server
2Push "use client" to the leaf — wrap the smallest interactive piece, not the whole pageKeeps the rest of the tree as RSC
3Strict TypeScript — no any; props typed via interface or type; shared shapes from lib/types.tsType safety
4All FastAPI calls through lib/api.ts — never inline fetch against NEXT_PUBLIC_API_URLSingle typed surface for the API
5Path aliases @/... — never ../../Maintainability
6Handle empty / loading / error — Suspense + loading.tsx, error.tsx, or inline guardsGood UX
7Tailwind for styling — match utility patterns in existing components; no inline style={{...}} for layoutConsistency
8No service-role secrets in frontend/ — admin tokens come from the user's Supabase session, not from a server keyService-role key is backend-only
9next/image for product images, next/link for navPerf + prefetch
10Cart state via useCart() from components/CartProvider.tsx — don't roll your ownSingle source of truth, persisted in localStorage

When to Use

  • Creating a new storefront or admin page
  • Building a reusable component
  • Adding a form (login, checkout, product create/edit, status update)
  • Refactoring a Client Component back to a Server Component when feasible

Quick Start

Server Component page (storefront)

// frontend/app/(store)/products/[slug]/page.tsx
import { notFound } from "next/navigation";
import { getProduct } from "@/lib/api";
import { AddToCartButton } from "@/components/AddToCartButton";

export default async function ProductPage({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  let product;
  try {
    product = await getProduct(slug);
  } catch {
    notFound();
  }
  return (
    <article className="grid gap-6 md:grid-cols-2">
      {/* ... */}
      <AddToCartButton product={product} />
    </article>
  );
}

Server Component admin page (gated)

// frontend/app/admin/products/page.tsx
import { redirect } from "next/navigation";
import { createSupabaseServerClient } from "@/lib/supabase/server";
import { adminListProducts } from "@/lib/api";

export default async function AdminProductsPage() {
  const supabase = await createSupabaseServerClient();
  const { data: { user } } = await supabase.auth.getUser();
  if (!user || user.app_metadata?.role !== "admin") redirect("/admin/login");

  const { data: { session } } = await supabase.auth.getSession();
  const { items } = await adminListProducts(session!.access_token, { limit: 100 });

  return <ProductsTable items={items} />; // ProductsTable can be a Client Component
}

Client Component with handlers

// frontend/components/AddToCartButton.tsx
"use client";

import { useCart } from "@/components/CartProvider";
import type { Product } from "@/lib/types";

export function AddToCartButton({ product }: { product: Product }) {
  const { add } = useCart();
  return (
    <button
      onClick={() => add(product, 1)}
      className="rounded bg-black px-4 py-2 text-white disabled:opacity-50"
      disabled={product.stock <= 0}
    >
      {product.stock > 0 ? "Add to cart" : "Sold out"}
    </button>
  );
}

Form (Client Component, plain React state)

"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { checkout, ApiError } from "@/lib/api";

export function CheckoutForm({ items }: { items: { product_id: string; quantity: number }[] }) {
  const router = useRouter();
  const [submitting, setSubmitting] = useState(false);
  const [error, setError] = useState<string | null>(null);

  async function onSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault();
    setSubmitting(true);
    setError(null);
    const fd = new FormData(e.currentTarget);
    try {
      const res = await checkout({
        email: String(fd.get("email")),
        shipping_name: String(fd.get("name")),
        shipping_address: {
          line1: String(fd.get("line1")),
          city: String(fd.get("city")),
          postal_code: String(fd.get("postal")),
          country: String(fd.get("country")),
        },
        items,
      });
      router.push(`/thank-you?order=${res.order_id}`);
    } catch (err) {
      setError(err instanceof ApiError ? String(err.detail) : "Checkout failed");
    } finally {
      setSubmitting(false);
    }
  }

  return (
    <form onSubmit={onSubmit} className="space-y-3">
      {/* ...inputs... */}
      {error && <p className="text-red-600">{error}</p>}
      <button disabled={submitting}>{submitting ? "Placing…" : "Place order"}</button>
    </form>
  );
}

File Organization

WhatWhere
Public storefront pagesfrontend/app/(store)/...
Admin pagesfrontend/app/admin/...
Reusable componentsfrontend/components/ (admin-specific in frontend/components/admin/)
Typesfrontend/lib/types.ts
FastAPI callsfrontend/lib/api.ts
Supabase clientsfrontend/lib/supabase/{client,server,middleware}.ts
Admin gatingfrontend/middleware.ts (matcher: /admin/:path*)

Choosing Server vs Client

Default to Server. Reach for "use client" only when the file needs:

  • useState / useReducer / useEffect / useContext
  • DOM event handlers (onClick, onSubmit, onChange)
  • Browser-only APIs (localStorage, window, IntersectionObserver)
  • A library that itself requires the client

When a page mostly renders server-fetched data but has one interactive widget, keep the page server and extract the widget into a Client Component file.

Data Fetching

  • Server Components: await listProducts(...) etc. directly. The wrapper sets cache: "no-store" for these dynamic admin/storefront calls; switch to next: { revalidate: N } per call when you want ISR
  • After a mutation in a Client Component, call router.refresh() to re-render parent Server Components against fresh data
  • Don't re-fetch the same thing in two places — fetch in the page, pass props down

Forms

  • Plain <form onSubmit> + useState is enough; do not add Formik/Zod for this MVP
  • Validate required fields inline; show ApiError.detail for server errors
  • For admin mutations, get the token from createSupabaseBrowserClient().auth.getSession() (or pass it down from the server-rendered parent)

Common Tasks

Add a Storefront Page

  1. Create frontend/app/(store)/<route>/page.tsx as async function (Server Component)
  2. Fetch via a typed helper from lib/api.ts
  3. Render with Tailwind; handle empty state
  4. Add loading.tsx / error.tsx siblings if the page is data-heavy

Add an Admin Page

  1. Create frontend/app/admin/<route>/page.tsx as a Server Component
  2. Use createSupabaseServerClient() + getUser(); redirect or 403 non-admins
  3. Get session.access_token, call admin* helper from lib/api.ts
  4. For interactivity, extract a "use client" child component

Add a Reusable Component

  1. Decide Server vs Client (default Server)
  2. Define interface Props
  3. Tailwind for styling; match the look of existing components
  4. Place in frontend/components/ or frontend/components/admin/

Add a Form

  1. New "use client" component
  2. Plain useState for fields, onSubmit handler
  3. Call the appropriate lib/api.ts helper
  4. Surface ApiError.detail; on success router.push() and/or router.refresh()

Additional Resources

Exemplar Files

  • frontend/app/(store)/page.tsx — Server Component with data fetch
  • frontend/app/admin/products/page.tsx — admin Server Component + auth check
  • frontend/components/CartProvider.tsx — Client Context (the cart state)
  • frontend/components/AddToCartButton.tsx — small leaf Client Component
  • frontend/components/ProductCard.tsx — Server Component card

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Supabase Postgres + Storage + Auth operations from FastAPI — service-role client, RLS-aware queries, additive SQL migrations under backend/db/migrations, the create_order RPC, and the product-images bucket. Use when adding tables/columns, writing queries, calling RPCs, or working with Storage.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

shanraisshan/learning-x-claude22026년 5월 7일 업데이트

Build FastAPI endpoints in the layered router → service → Supabase pattern, with Pydantic v2 schemas, JWT/admin gating, and consistent HTTPException-based domain errors. Use when adding/modifying any endpoint under backend/.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

shanraisshan/learning-x-claude22026년 5월 7일 업데이트

pytest + httpx + TestClient patterns for the FastAPI backend. Dependency overrides for auth, FakeSupabase mocking, HTTPException assertions. Use when writing or reviewing backend/tests/test_*.py.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

shanraisshan/learning-x-claude22026년 5월 7일 업데이트

Drive Chrome via the claude-in-chrome MCP to exercise plan.md::Verify steps for a shipped feature. Loads deferred MCP tool schemas, asserts console/network cleanliness, records a GIF for the PR. Used by the browser-verifier agent.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

shanraisshan/learning-x-claude22026년 5월 7일 업데이트

Pre-PR code review checklist for this repo. Plan alignment, security gates, layering, dead code, API-surface completeness. Used by the code-reviewer agent.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

shanraisshan/learning-x-claude22026년 5월 7일 업데이트

Integrate with the FastAPI backend through the typed wrapper in frontend/lib/api.ts, attach Supabase JWTs for admin calls, and handle errors via ApiError. Use when adding/modifying API calls or wiring auth.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

shanraisshan/learning-x-claude22026년 5월 7일 업데이트

shanraisshan의 스킬 모두 보기

이 스킬의 문제 신고