next-js/skills/skills/admin-crud-scaffold/SKILL.md
Scaffold a complete admin CRUD feature with all required files: API service, validation models, table columns, admin pages (list/add/edit), admin components (header, table, filters, actions cell), and form components (shared form, add form, edit form). Use this skill whenever the user wants to add a new admin section, create CRUD pages, scaffold entity management, add an admin table, build admin forms for a new resource, or mentions "scaffold", "generate", "create admin pages for", "add management for", or "CRUD for [entity]". This is the most common development pattern in the project — trigger it proactively when the user describes any new entity that needs admin management.
npx skillsauth add spuneiartur/claude-agent-specs admin-crud-scaffoldInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Generate the complete file set for a new admin CRUD entity. This pattern repeats across every managed entity in the project (articles, categories, products, tags, textures, portfolios, etc.) and requires ~15 coordinated files across 6 directories.
Ask the user for:
From the entity name, derive:
{entity} — lowercase singular (e.g., testimonial){Entity} — PascalCase singular (e.g., Testimonial){entities} — lowercase plural (e.g., testimonials){Entities} — PascalCase plural (e.g., Testimonials)Generate all files in order. Read references/patterns.md for exact code templates.
api/{entity}.jsimport { axiosAuth } from '@lib';
export const get{Entity} = (id) => axiosAuth.get(`/admin/{entities}/${id}`);
export const create{Entity} = (data) => axiosAuth.post('/admin/{entities}', data);
export const update{Entity} = ({ id, data }) => axiosAuth.put(`/admin/{entities}/${id}`, data);
export const delete{Entity} = (id) => axiosAuth.delete(`/admin/{entities}/${id}`);
models/{entity}.jsExport validationSchema (Yup.object().shape({...})) and initialValues (plain object). Use Yup validators matching each field's type and requirements.
models/{entity}-filters.jsExport validationSchema and initialValues for filter fields (typically search, status, date ranges). All filter fields are optional strings.
data/{entity}-columns.jsArray of column objects with Header, accessor, Cell, extraClass, disableSortBy. The last column must be the actions cell with accessor: '_id' and Cell: {Entity}ActionsCell. Update the barrel export in data/index.js.
pages/admin/{entities}/index.jsimport { checkAuth, withAuth } from '@auth';
import { Layout } from '@components';
import { {Entity}Header, {Entity}Table } from '@components/Admin/{Entity}';
import { useState } from 'react';
const Page = () => {
const [options, setOptions] = useState({});
return (
<Layout title="{Entities}">
<{Entity}Header setOptions={setOptions} />
<{Entity}Table options={options} />
</Layout>
);
};
export async function getServerSideProps(context) {
return await checkAuth(context);
}
export default withAuth(Page);
pages/admin/{entities}/add/index.jsimport { checkAuth, withAuth } from '@auth';
import { Layout } from '@components';
import { Add{Entity}Form } from '@components/Forms';
const Page = () => (
<Layout title="Add {Entity}">
<Add{Entity}Form />
</Layout>
);
export async function getServerSideProps(context) {
return await checkAuth(context);
}
export default withAuth(Page);
pages/admin/{entities}/edit/[id].jsUses useQuery to fetch the entity by router.query.id. Renders loading/error/success states. Passes the entity data to Edit{Entity}Form.
import { checkAuth, withAuth } from '@auth';
import { Button, Layout } from '@components';
import { Edit{Entity}Form } from '@components/Forms';
import { useQuery } from '@hooks';
import { useRouter } from 'next/router';
const Page = () => {
const router = useRouter();
const { id } = router.query;
const { data: {entity}, status } = useQuery(`admin/{entities}/${id}`);
return (
<Layout title="Edit {Entity}">
{status === 'loading' && <div>Loading...</div>}
{status === 'error' && <div>Error loading {entity}.</div>}
{status === 'success' && <Edit{Entity}Form {entity}={{entity}} />}
</Layout>
);
};
export async function getServerSideProps(context) {
return await checkAuth(context);
}
export default withAuth(Page);
components/Admin/{Entity}/Create a directory with these files:
{Entity}Header.jsx — Title, add button (links to add page), filters section.
import { Button } from '@components';
import { {Entity}Filters } from '@components/Admin/{Entity}';
const {Entity}Header = ({ setOptions }) => (
<div className="bg-white rounded-xl shadow-sm border border-metal-200 p-6 mb-6">
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6">
<div>
<h1 className="text-2xl font-bold text-gray-900">{Entities}</h1>
<p className="text-gray-600">Manage {entities}</p>
</div>
<Button href="/admin/{entities}/add" className="px-4 py-2.5 bg-gradient-to-r from-accent to-accent/90 text-white rounded-lg hover:from-accent/90 hover:to-accent/80 transition-all duration-200 flex items-center space-x-2 shadow-sm hover:shadow-md font-medium">
<i className="fas fa-plus text-sm"></i>
<span>Add {Entity}</span>
</Button>
</div>
<div className="pt-4 border-t border-gray-100">
<{Entity}Filters setOptions={setOptions} />
</div>
</div>
);
export default {Entity}Header;
{Entity}Table.jsx — Uses useInfiniteQuery with table components.
import { LoadMoreOnClick } from '@components/Buttons';
import { TableColumns, TableError, TableLoading, TableSuccess } from '@components/Tables';
import { {entity}Columns } from '@data';
import { useInfiniteQuery } from '@hooks';
const {Entity}Table = ({ options }) => {
const { data, status, ...props } = useInfiniteQuery('admin/{entities}', options);
return (
<>
<TableColumns pageParams={data?.pageParams} />
{status === 'loading' && <TableLoading name="{entities}" columns={{entity}Columns} />}
{status === 'error' && <TableError name="{entities}" columns={{entity}Columns} />}
{status === 'success' && (
<>
<TableSuccess name="{entities}" columns={{entity}Columns} data={data} {...props} />
<div className="px-4 sm:p-4"><LoadMoreOnClick {...props} /></div>
</>
)}
</>
);
};
export default {Entity}Table;
{Entity}Filters.jsx — Uses AutoSubmitFilterForm with filter model.
import { Search, Dropdown, DatePicker } from '@components/Fields';
import { Field } from '@components/HookForm';
import AutoSubmitFilterForm from '@components/HookForm/AutoSubmitFilterForm';
import { initialValues, validationSchema } from '@models/{entity}-filters';
const {Entity}Filters = ({ setOptions }) => (
<AutoSubmitFilterForm initialValues={initialValues} validationSchema={validationSchema} onSubmit={setOptions}>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
<Field as={Search} name="search" placeholder="Search {entities}..." label="Search" />
{/* Add filter fields based on entity requirements */}
</div>
</AutoSubmitFilterForm>
);
export default {Entity}Filters;
{Entity}ActionsCell.jsx — Edit link + delete with confirmation.
import { delete{Entity} } from '@api/{entity}';
import { AreYouSure, Button } from '@components';
import { useDisclosure, useMutation } from '@hooks';
const {Entity}ActionsCell = ({ value: id, row: { original } }) => {
const { isOpen, show, hide } = useDisclosure();
const mutation = useMutation(delete{Entity}, { invalidateQueries: 'admin/{entities}' });
const handleDelete = async () => { await mutation.mutateAsync(id); hide(); };
return (
<div className="flex items-center justify-center space-x-2">
<Button href={`/admin/{entities}/edit/${id}`} className="text-primary border-primary hover:bg-primary hover:text-white w-8 h-8 p-0 flex items-center justify-center rounded-md" title="Edit">
<i className="fas fa-edit text-xs"></i>
</Button>
<Button onClick={show} className="text-red-600 border-red-600 hover:bg-red-600 hover:text-white w-8 h-8 p-0 flex items-center justify-center rounded-md" title="Delete">
<i className="fas fa-trash text-xs"></i>
</Button>
<AreYouSure isOpen={isOpen} hide={hide} onConfirm={handleDelete} title="Confirm deletion" message={`Are you sure you want to delete "${original.title || original.name}"?`} isLoading={mutation.isPending} />
</div>
);
};
export default {Entity}ActionsCell;
index.js — Barrel exports for all components in the directory.
components/Forms/{Entity}Form.jsx — Shared form fields using Field with appropriate field components from @components/Fields. Uses useFormContext if needed for conditional logic.
Add{Entity}Form.jsx — Wraps {Entity}Form with HookForm/Form/Submit. Uses useMutation(create{Entity}, { invalidateQueries: 'admin/{entities}', successCallback: () => router.push('/admin/{entities}') }).
Edit{Entity}Form.jsx — Same structure, receives {entity} as prop, uses initialValues={{entity}} and useMutation(update{Entity}) passing { id: {entity}._id, data }.
Add exports to:
data/index.js: export { default as {entity}Columns } from './{entity}-columns';components/Forms/index.js: export { default as Add{Entity}Form } from './Add{Entity}Form'; and export { default as Edit{Entity}Form } from './Edit{Entity}Form';{Entity}FormLoading.jsx and {Entity}FormError.jsx in components/Admin/{Entity}/ using Bone component for loading skeletons. The edit page uses these for the useQuery loading/error states.<i className="fas fa-plus"></i> format.p-[50px] syntax.examples/ folder first — look for similar implementations before generating./admin/{entities}) must existtools
Replace with description of the skill and when Claude should use it.
tools
Comprehensive website performance audit and optimization skill. Identifies and automatically fixes performance issues including image optimization, video compression, lazy loading, Core Web Vitals, bundle size, and rendering strategy. Uses Lighthouse (via CLI or MCP when available), ffmpeg for media processing, and the project's existing Image component with blur-up lazy loading. Use this skill whenever the user mentions: website speed, page load time, performance audit, Core Web Vitals, Lighthouse, optimize images, compress videos, lazy loading, LCP, CLS, FID, INP, slow website, speed up, performance optimization, image compression, video optimization, blur placeholder, WebP conversion, media audit, bundle size, or wants to improve their website's loading performance. Also trigger when the user says "my site is slow", "optimize for speed", "reduce load time", "improve performance", or asks about image/video optimization in any context.
tools
Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.
tools
Suite of tools for creating elaborate, multi-component claude.ai HTML artifacts using modern frontend web technologies (React, Tailwind CSS, shadcn/ui). Use for complex artifacts requiring state management, routing, or shadcn/ui components - not for simple single-file HTML/JSX artifacts.