원클릭으로
frontend
Frontend development rules and patterns for Next.js/React projects
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Frontend development rules and patterns for Next.js/React projects
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Creates or adds Convex to an app. Use for new Convex projects, npm create convex@latest, frontend setup, env vars, or the first npx convex dev run.
Builds reusable Convex components with isolated tables and app-facing APIs. Use for new components, reusable backend modules, integrations, or component boundary work.
Audits Convex performance for reads, subscriptions, write contention, and function limits. Use for slow features, insights findings, OCC conflicts, or read amplification.
Sets up Convex auth, identity mapping, and access control. Use for login, auth providers, users tables, protected functions, or roles in a Convex app.
| name | frontend |
| description | Frontend development rules and patterns for Next.js/React projects |
components/components/ui/components/[feature]/Dialog → Integrated → Form:
FooDialog.tsx - Dialog wrapper with trigger buttonFooIntegrated.tsx - Form logic + submission handlingFooForm.tsx - Pure form with react-hook-form + zod validationhooks/cn helper for conditional and dynamic classname concatenationconst formatPrice = (price: number) =>
new Intl.NumberFormat("en-NG", {
style: "currency",
currency: "NGN",
maximumFractionDigits: 0,
}).format(price);
Returns: "₦1,500"
// Query
const data = useQuery(api.myFunction, { arg });
// Mutation
const mutate = useMutation(api.myMutation);
mutate({ arg });
import { useForm } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
const schema = z.object({
name: z.string().min(1, "Required"),
});
const { register, handleSubmit } = useForm({
resolver: zodResolver(schema),
});
AlertDialog for destructive actionsDialog for formsTabs, TabsList, TabsTrigger, TabsContentInstead of repeated find() calls in JSX:
// Bad - repeated find in JSX
<Form initialData={currentPlanId ? plans.find(p => p.id === currentPlanId) : undefined} />
// Good - extract to variable
const currentPlan = plans.find(p => p.id === currentPlanId);
<Form initialData={currentPlan} />
const computed = React.useMemo(() => expensiveOperation(data), [data]);
Return early to reduce nesting:
if (!data) return <Skeleton />;
return ( /* main content */ );
Avoid creating separate state for derived values:
// Bad - duplicate state
const [filtered, setFiltered] = useState(data);
useEffect(() => setFiltered(data.filter(...)), [data]);
// Good - derive in render
const filtered = data.filter(...);
// Bad
<Component a={obj.a} b={obj.b} c={obj.c} d={obj.d} />
// Good
const { a, b, c, d } = obj;
<Component a={a} b={b} c={c} d={d} />
// Or as spread
<Component {...pick(obj, 'a', 'b', 'c', 'd')} />
Forms should use local isSubmitting state passed to isLoading prop:
const [isSubmitting, setIsSubmitting] = useState(false);
<Form
isLoading={isSubmitting}
onSubmit={async (values) => {
setIsSubmitting(true);
try {
await mutation(values);
} finally {
setIsSubmitting(false);
}
}}
/>
// Form button shows "Saving..." when isLoading=true
Inline functions cause child components to rerender on every parent render:
// Bad - creates new function each render
<Button onClick={() => handleClick(id)} />
// Good - use stable handler, pass id as argument
<Button onClick={handleClick} payload={id} />
Prefer map with direct access over multiple/nested ternary operations for JavaScript conditionals.
// Bad - nested ternaries
const value = var === "a" ? 1 : var === "b" ? 2 : var === "c" ? 3 : "default";
// Good - map lookup
const valueMap = {
"a": 1,
"b": 2,
"c": 3
};
const value = valueMap[var] || "default";
// Show/Hide with && (for no else case)
{isOpen && <Content />}
// Ternary for else case
{isOpen ? <OpenState /> : <ClosedState />}
Group related components for cleaner exports:
// Instead of
export { Button } from "./button";
export { ButtonGroup } from "./button-group";
// Use compound component
export const Button = Object.assign(ButtonRoot, { Group: ButtonGroup });
// Usage: <Button.Group>