一键导入
shadcn-stack
Next.js 15 + shadcn/ui + Server Components patterns for server-rendered React applications
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Next.js 15 + shadcn/ui + Server Components patterns for server-rendered React applications
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Add email verification, password reset, and account management emails to Better Auth using Resend.
TypeScript authentication framework (framework-agnostic). Features: email/password, OAuth (Google, GitHub, Discord), 2FA (TOTP, SMS), passkeys/WebAuthn, session management, RBAC, rate limiting, database adapters. Actions: implement, configure, secure authentication systems. Keywords: Better Auth, authentication, authorization, OAuth, email/password, 2FA, MFA, TOTP, passkeys, WebAuthn, session management, RBAC, rate limiting, database adapter, TypeScript auth, social login, Google auth, GitHub auth, Discord auth, email verification, password reset. Use when: implementing TypeScript auth, adding OAuth providers, setting up 2FA/MFA, managing sessions, configuring RBAC, building secure auth systems.
Build websites and web applications using Telefonica's Mistica design system React components. Use this skill when writing React UI code with @telefonica/mistica, creating pages, layouts, forms, or any user interface that should follow Telefonica's design guidelines. Triggers on tasks involving Mistica components, Telefonica branding, or when the user mentions Mistica.
Provides shadcn/ui component library expertise for React applications with shadcn/cli v4, preset system, RTL support, unified radix-ui, and OKLCH theming. Use when implementing UI components, design systems, CLI workflows, or registry authoring with shadcn/ui.
React 19 performance optimization guidelines for concurrent rendering, Server Components, actions, hooks, and memoization (formerly react-19). This skill should be used when writing React 19 components, using concurrent features, or optimizing re-renders. This skill does NOT cover Next.js-specific features like App Router, next.config.js, or Next.js caching (use nextjs-16-app-router skill). For client-side form validation with React Hook Form, use react-hook-form skill.
Implement, review, debug, and refactor TanStack Start React Server Components in React 19 apps. Use when tasks mention @tanstack/react-start/rsc, renderServerComponent, createCompositeComponent, CompositeComponent, renderToReadableStream, createFromReadableStream, createFromFetch, Composite Components, React Flight streams, loader or query owned RSC caching, router.invalidate, structuralSharing: false, selective SSR, stale names like renderRsc or .validator, or migration from Next App Router RSC patterns. Do not use for generic SSR or non-TanStack RSC frameworks except brief comparison.
| name | shadcn-stack |
| description | Next.js 15 + shadcn/ui + Server Components patterns for server-rendered React applications |
| agents | ["blaze","nova","tap","spark"] |
| context7_libraries | ["/vercel/next.js","/shadcn-ui/ui","/tanstack/query","/react-hook-form/react-hook-form","/effect-ts/effect"] |
| llm_docs | ["shadcn","tanstack","effect"] |
Modern Next.js architecture optimized for server-side rendering, SEO, and progressive enhancement.
| Library | Purpose | Install |
|---|---|---|
| Next.js 15 | Full-stack React framework | next |
| shadcn/ui | Accessible UI components | npx shadcn@latest add [component] |
| React Query | Client-side data caching | @tanstack/react-query |
| React Hook Form | Form state management | react-hook-form |
| Effect | Type-safe validation & errors | effect, @hookform/resolvers |
| Tailwind CSS | Utility-first styling | tailwindcss |
app/
├── layout.tsx # Root layout
├── page.tsx # Home page (/)
├── loading.tsx # Loading UI
├── error.tsx # Error boundary
├── dashboard/
│ ├── layout.tsx # Dashboard layout
│ ├── page.tsx # /dashboard
│ └── settings/
│ └── page.tsx # /dashboard/settings
└── api/
└── users/
└── route.ts # API route
// app/users/page.tsx - Server Component by default
import { getUsers } from '@/lib/db';
export default async function UsersPage() {
const users = await getUsers(); // Direct database access
return (
<div>
<h1>Users</h1>
<UserList users={users} />
</div>
);
}
// components/user-search.tsx
'use client';
import { useState } from 'react';
import { Input } from '@/components/ui/input';
export function UserSearch({ onSearch }: { onSearch: (query: string) => void }) {
const [query, setQuery] = useState('');
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setQuery(e.target.value);
onSearch(e.target.value);
};
return (
<Input
value={query}
onChange={handleChange}
placeholder="Search users..."
/>
);
}
// app/actions/users.ts
'use server';
import { revalidatePath } from 'next/cache';
import { Schema, Effect } from 'effect';
import { db } from '@/lib/db';
const CreateUserSchema = Schema.Struct({
name: Schema.String.pipe(Schema.minLength(2)),
email: Schema.String.pipe(Schema.pattern(/^[^@]+@[^@]+\.[^@]+$/)),
role: Schema.Literal('admin', 'user', 'guest'),
});
export async function createUser(formData: FormData) {
const validated = Schema.decodeUnknownSync(CreateUserSchema)({
name: formData.get('name'),
email: formData.get('email'),
role: formData.get('role'),
});
await db.user.create({ data: validated });
revalidatePath('/users');
}
export async function deleteUser(id: string) {
await db.user.delete({ where: { id } });
revalidatePath('/users');
}
// app/users/create/page.tsx
import { createUser } from '@/app/actions/users';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
export default function CreateUserPage() {
return (
<form action={createUser}>
<Input name="name" placeholder="Name" required />
<Input name="email" type="email" placeholder="Email" required />
<select name="role">
<option value="user">User</option>
<option value="admin">Admin</option>
</select>
<Button type="submit">Create User</Button>
</form>
);
}
'use client';
import { useFormStatus } from 'react-dom';
import { Button } from '@/components/ui/button';
function SubmitButton() {
const { pending } = useFormStatus();
return (
<Button type="submit" disabled={pending}>
{pending ? 'Creating...' : 'Create User'}
</Button>
);
}
npx shadcn@latest init
npx shadcn@latest add button card input form table dialog
import { Button } from '@/components/ui/button';
import { Card, CardHeader, CardTitle, CardContent, CardFooter } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
export function UserCard({ user }: { user: User }) {
return (
<Card>
<CardHeader>
<CardTitle>{user.name}</CardTitle>
</CardHeader>
<CardContent>
<p className="text-muted-foreground">{user.email}</p>
</CardContent>
<CardFooter>
<Button variant="outline">Edit</Button>
<Button variant="destructive">Delete</Button>
</CardFooter>
</Card>
);
}
'use client';
import { useState } from 'react';
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
export function CreateUserDialog() {
const [open, setOpen] = useState(false);
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button>Create User</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Create New User</DialogTitle>
<DialogDescription>
Add a new user to the system.
</DialogDescription>
</DialogHeader>
<CreateUserForm onSuccess={() => setOpen(false)} />
</DialogContent>
</Dialog>
);
}
'use client';
import { useForm } from 'react-hook-form';
import { effectTsResolver } from '@hookform/resolvers/effect-ts';
import { Schema } from 'effect';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
const formSchema = Schema.Struct({
name: Schema.String.pipe(Schema.minLength(2, { message: () => 'Name must be at least 2 characters' })),
email: Schema.String.pipe(Schema.pattern(/^[^@]+@[^@]+\.[^@]+$/, { message: () => 'Invalid email address' })),
});
type FormValues = Schema.Schema.Type<typeof formSchema>;
export function CreateUserForm({ onSuccess }: { onSuccess: () => void }) {
const form = useForm<FormValues>({
resolver: effectTsResolver(formSchema),
defaultValues: { name: '', email: '' },
});
async function onSubmit(values: FormValues) {
const response = await fetch('/api/users', {
method: 'POST',
body: JSON.stringify(values),
});
if (response.ok) {
onSuccess();
}
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input placeholder="John Doe" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input type="email" placeholder="john@example.com" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" disabled={form.formState.isSubmitting}>
{form.formState.isSubmitting ? 'Creating...' : 'Create'}
</Button>
</form>
</Form>
);
}
'use client';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
export function useUsers() {
return useQuery({
queryKey: ['users'],
queryFn: async () => {
const response = await fetch('/api/users');
return response.json();
},
});
}
export function useCreateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (newUser: CreateUserInput) => {
const response = await fetch('/api/users', {
method: 'POST',
body: JSON.stringify(newUser),
});
return response.json();
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['users'] });
},
});
}
// app/providers.tsx
'use client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useState } from 'react';
export function Providers({ children }: { children: React.ReactNode }) {
const [queryClient] = useState(() => new QueryClient());
return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
);
}
// app/layout.tsx
import { Providers } from './providers';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}
// app/users/page.tsx
import { getUsers } from '@/lib/db';
import { UsersTable } from './users-table';
export default async function UsersPage() {
const users = await getUsers();
return <UsersTable data={users} />;
}
// app/users/users-table.tsx
'use client';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { deleteUser } from '@/app/actions/users';
export function UsersTable({ data }: { data: User[] }) {
return (
<Table>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Email</TableHead>
<TableHead>Role</TableHead>
<TableHead>Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data.map((user) => (
<TableRow key={user.id}>
<TableCell>{user.name}</TableCell>
<TableCell>{user.email}</TableCell>
<TableCell>
<Badge variant={user.role === 'admin' ? 'default' : 'secondary'}>
{user.role}
</Badge>
</TableCell>
<TableCell>
<form action={deleteUser.bind(null, user.id)}>
<Button variant="destructive" size="sm" type="submit">
Delete
</Button>
</form>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
);
}
// app/users/loading.tsx
import { Skeleton } from '@/components/ui/skeleton';
export default function Loading() {
return (
<div className="space-y-4">
<Skeleton className="h-8 w-48" />
<Skeleton className="h-64 w-full" />
</div>
);
}
// app/users/error.tsx
'use client';
import { Button } from '@/components/ui/button';
export default function Error({
error,
reset,
}: {
error: Error;
reset: () => void;
}) {
return (
<div className="flex flex-col items-center gap-4 py-16">
<h2 className="text-xl font-semibold">Something went wrong!</h2>
<p className="text-muted-foreground">{error.message}</p>
<Button onClick={reset}>Try again</Button>
</div>
);
}
// app/users/page.tsx
import { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Users | My App',
description: 'Manage users in the system',
openGraph: {
title: 'Users',
description: 'Manage users in the system',
},
};
export default async function UsersPage() {
// ...
}
// app/users/[id]/page.tsx
import { Metadata } from 'next';
import { getUser } from '@/lib/db';
type Props = { params: Promise<{ id: string }> };
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { id } = await params;
const user = await getUser(id);
return {
title: `${user.name} | My App`,
description: `Profile for ${user.name}`,
};
}