用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/diegosouzapw/awesome-omni-skill --skill web-frontend命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
基于 SOC 职业分类
正在显示 SKILL.md
| name | web-frontend |
| description | Next.js App Router, Server/Client components, shadcn/ui patterns, Tailwind CSS conventions |
Decision Tree:
Does it use hooks? (useState, useEffect, etc.) → Client Component
Does it handle events? (onClick, onChange, etc.) → Client Component
Does it use browser APIs? (window, localStorage, etc.) → Client Component
Does it use third-party client libraries? → Client Component
Otherwise → Server Component (default)
Examples:
// ✅ Server Component - Data fetching
export default async function ActivityPage({
params,
}: {
params: { id: string };
}) {
const supabase = createClient();
const { data: activity } = await supabase
.from("activities")
.select("*")
.eq("id", params.id)
.single();
return <ActivityDetail activity={activity} />;
}
// ✅ Client Component - Interactive
("use client");
export function ActivityChart({ data }: Props) {
const [metric, setMetric] = useState("heartRate");
return (
<div>
<select onChange={(e) => setMetric(e.target.value)}>
<option value="heartRate">Heart Rate</option>
</select>
<Chart data={data} metric={metric} />
</div>
);
}
When to use: Client-side data fetching Why: Type-safe, automatic cache management
"use client";
import { trpc } from "@/lib/trpc";
export function ActivitiesList() {
const { data, isLoading, error, refetch } = trpc.activities.list.useQuery(
{ limit: 20, offset: 0 },
{
staleTime: 5 * 60 * 1000, // 5 minutes
refetchOnWindowFocus: false,
},
);
if (isLoading) return <Skeleton />;
if (error) return <ErrorAlert message={error.message} />;
return (
<div>
{data?.map((activity) => (
<ActivityCard key={activity.id} activity={activity} />
))}
</div>
);
}
When to use: Create/update/delete operations Why: Instant UI feedback, automatic rollback on error
const utils = trpc.useUtils();
const mutation = trpc.activities.update.useMutation({
onMutate: async (updatedActivity) => {
await utils.activities.list.cancel();
const previousActivities = utils.activities.list.getData();
utils.activities.list.setData(undefined, (old) =>
old?.map((act) =>
act.id === updatedActivity.id ? { ...act, ...updatedActivity } : act,
),
);
return { previousActivities };
},
onError: (err, vars, context) => {
utils.activities.list.setData(undefined, context?.previousActivities);
},
onSettled: () => {
utils.activities.list.invalidate();
},
});
When to use: Forms with validation, field arrays, dynamic fields Why: Type-safe, automatic error handling, Zod integration
"use client";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { activitySchema } from "@repo/core/schemas";
export function ActivityForm() {
const form = useForm({
resolver: zodResolver(activitySchema),
defaultValues: { name: "", type: "run", distance: 0 },
});
const mutation = trpc.activities.create.useMutation({
onSuccess: () => toast.success("Activity created"),
onError: (error) => {
if (error.data?.zodError) {
const fieldErrors = error.data.zodError.fieldErrors;
Object.entries(fieldErrors).forEach(([field, messages]) => {
form.setError(field as any, { : messages?.[] });
});
}
},
});
(
);
}
When to use: Dashboard pages, user-specific content Why: Automatic redirect to login, loading states
// Layout for protected routes
export default function DashboardLayout({ children }: Props) {
return (
<AuthGuard>
<div className="min-h-screen bg-background">
<Sidebar />
<main className="ml-64">{children}</main>
</div>
</AuthGuard>
);
}
// AuthGuard component
("use client");
export function AuthGuard({ children }: { children: React.ReactNode }) {
const { data: session, isLoading } = useSession();
if (isLoading) return <LoadingScreen />;
if (!session) redirect("/login");
return <>{children}</>;
}
When to use: UI components needing consistent styling Why: Pre-styled, accessible, customizable
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
<Card>
<CardHeader>
<CardTitle>Activity Details</CardTitle>
</CardHeader>
<CardContent>
<Button variant="default">Save</Button>
<Button variant="outline">Cancel</Button>
</CardContent>
</Card>;
// ❌ BAD
export default function Page() {
const [state, setState] = useState(0); // Error!
return <div>{state}</div>;
}
// ✅ CORRECT
("use client");
export default function Page() {
const [state, setState] = useState(0);
return <div>{state}</div>;
}
// ❌ BAD
"use client";
export default function Page() {
const [data, setData] = useState(null);
useEffect(() => {
fetch("/api/data")
.then((r) => r.json())
.then(setData);
}, []);
}
// ✅ CORRECT - Use tRPC
("use client");
export default function Page() {
const { data } = trpc.getData.useQuery();
}
// ✅ OR use Server Component
export default async function Page() {
const data = await fetchData();
return <ClientComponent data={data} />;
}
// ❌ BAD
const mutation = trpc.activities.create.useMutation({
onSuccess: () => {
toast.success("Created");
// Forgot to invalidate cache!
},
});
// ✅ CORRECT
const utils = trpc.useUtils();
const mutation = trpc.activities.create.useMutation({
onSuccess: () => {
utils.activities.list.invalidate();
toast.success("Created");
},
});
apps/web/
├── app/
│ ├── (marketing)/ # Public pages
│ │ ├── page.tsx # Landing page
│ │ └── _layout.tsx
│ ├── (dashboard)/ # Protected dashboard
│ │ ├── activities/
│ │ │ ├── page.tsx
│ │ │ └── [id]/page.tsx
│ │ └── _layout.tsx
│ ├── api/ # API routes
│ └── globals.css
├── components/
│ ├── ui/ # shadcn/ui components
│ └── shared/ # Shared components
└── lib/
├── trpc.ts # tRPC client
└── utils.ts
PascalCase → ActivityCard.tsxcamelCase → formatDate.tscamelCase with use → useAuth.tskebab-case → activity-detail/kebab-case → webhook-handler/// app/(dashboard)/activities/page.tsx
import { AuthGuard } from "@/components/AuthGuard";
import { ActivitiesList } from "@/components/ActivitiesList";
export default function ActivitiesPage() {
return (
<AuthGuard>
<div className="container py-8">
<h1 className="text-3xl font-bold mb-6">Activities</h1>
<ActivitiesList />
</div>
</AuthGuard>
);
}
// app/api/auth/callback/route.ts
import { NextRequest, NextResponse } from "next/server";
export async function GET(request: NextRequest) {
const code = request.nextUrl.searchParams.get("code");
if (!code) {
return NextResponse.redirect("/login?error=no_code");
}
// Exchange code for tokens
const tokens = await exchangeCodeForTokens(code);
// Store tokens
await storeTokens(userId, tokens);
return NextResponse.redirect("/dashboard");
}
Required:
next v15+react v19+@tanstack/react-query v5@trpc/client, @trpc/server, @trpc/react-querytailwindcss v4zodOptional:
react-hook-form + @hookform/resolverssonner (toast notifications)Next Review: 2026-02-21