Software_development_department senior-frontend
Next.js App Router specific patterns — Server Components, Client Components boundary, parallel fetching, bundle analysis, a11y. Use ONLY for Next.js 13+ App Router projects. For generic React/Vue patterns, use `frontend-patterns` instead.
install
source · Clone the upstream repo
git clone https://github.com/tranhieutt/software_development_department
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/tranhieutt/software_development_department "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.claude/skills/senior-frontend" ~/.claude/skills/tranhieutt-software-development-department-senior-frontend && rm -rf "$T"
manifest:
.claude/skills/senior-frontend/SKILL.mdsource content
Senior Frontend
Critical rules (non-obvious)
- Always
in server components before client boundary — mixing async server + client state without boundaries causes hydration mismatchesreturn
on LCP images only — addingpriority
everywhere defeats preload budgetspriority
at the leaf, not the root — push client boundary as deep as possible to maximize RSC treeuse client- Parallel data fetching in Server Components: use
at the page level, not sequential awaitsPromise.all([...]) - Bundle heavy deps:
(290KB) →moment
(2KB);dayjs
→lodash
with tree-shaking;lodash-es
→ nativeaxiosfetch
Next.js: server vs client boundary
// Server Component (default) — fetch directly, no hooks async function ProductPage({ params }: { params: { id: string } }) { const [product, reviews] = await Promise.all([ // parallel fetch getProduct(params.id), getReviews(params.id), ]); return ( <div> <h1>{product.name}</h1> <Suspense fallback={<ReviewsSkeleton />}> <Reviews productId={params.id} /> {/* can defer slow queries */} </Suspense> <AddToCartButton productId={product.id} /> {/* client boundary at leaf */} </div> ); } // Client Component — only where interactivity needed "use client"; function AddToCartButton({ productId }: { productId: string }) { const [adding, setAdding] = useState(false); return <button onClick={() => addToCart(productId)}>Add to Cart</button>; }
Next.js: config essentials
// next.config.js const nextConfig = { images: { remotePatterns: [{ hostname: "cdn.example.com" }], formats: ["image/avif", "image/webp"], }, experimental: { optimizePackageImports: ["lucide-react", "@heroicons/react"], // tree-shake icon libs }, };
Component: TypeScript patterns
// Generic list component function List<T extends { id: string }>({ items, renderItem }: { items: T[]; renderItem: (item: T) => React.ReactNode; }) { return <ul>{items.map(item => <li key={item.id}>{renderItem(item)}</li>)}</ul>; } // Props extending HTML element interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> { variant?: "primary" | "ghost" | "danger"; isLoading?: boolean; } export function Button({ variant = "primary", isLoading, children, ...props }: ButtonProps) { return ( <button {...props} disabled={props.disabled || isLoading} aria-busy={isLoading} className={cn("px-4 py-2 rounded font-medium focus-visible:ring-2", variant === "primary" && "bg-blue-600 text-white hover:bg-blue-700", variant === "danger" && "bg-red-600 text-white", (props.disabled || isLoading) && "opacity-50 cursor-not-allowed" )}> {isLoading && <Spinner aria-hidden />} {children} </button> ); }
Performance: bundle analysis
Common heavy deps to replace:
| Package | Size | Alternative |
|---|---|---|
| moment | 290KB | (2KB) or (12KB) |
| lodash | 71KB | (tree-shakeable) |
| axios | 14KB | native or (3KB) |
| @mui/material | Large | shadcn/ui or Radix UI |
# Analyze bundle npx @next/bundle-analyzer # or npx vite-bundle-visualizer
Accessibility essentials
// Skip link — place before main nav <a href="#main-content" className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4"> Skip to main content </a> // Icon button — always label <button type="button" aria-label="Close dialog" className="focus-visible:ring-2"> <XIcon aria-hidden="true" /> </button> // Minimum contrast: 4.5:1 for text, 3:1 for UI components
Project structure (Next.js App Router)
app/ ├── layout.tsx # Root layout: fonts, providers, metadata ├── page.tsx ├── (auth)/ # Route group — no URL segment │ ├── login/page.tsx │ └── register/page.tsx └── api/ └── [route]/route.ts components/ ├── ui/ # Button, Input, Card (reusable primitives) └── features/ # Domain-specific composites hooks/ # useDebounce, useLocalStorage, useMediaQuery lib/ ├── utils.ts # cn(), formatDate() └── api.ts # API client types/ # Shared TypeScript types