--- name: senior-frontend type: reference description: "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." paths: ["**/app/**/*.tsx", "**/app/**/*.jsx", "**/next.config.*", "**/app/layout.tsx"] when_to_use: "When building Next.js 13+ App Router applications with Server Components, NOT for generic React/Vue (see `frontend-patterns`)" allowed-tools: Read, Glob, Grep, Write, Edit, Bash user-invocable: true effort: 3 --- # Senior Frontend ## Critical rules (non-obvious) - **Always `return` in server components before client boundary** — mixing async server + client state without boundaries causes hydration mismatches - **`priority` on LCP images only** — adding `priority` everywhere defeats preload budgets - **`use client` at the leaf, not the root** — push client boundary as deep as possible to maximize RSC tree - **Parallel data fetching in Server Components**: use `Promise.all([...])` at the page level, not sequential awaits - **Bundle heavy deps**: `moment` (290KB) → `dayjs` (2KB); `lodash` → `lodash-es` with tree-shaking; `axios` → native `fetch` ## Next.js: server vs client boundary ```tsx // 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 (

{product.name}

}> {/* can defer slow queries */} {/* client boundary at leaf */}
); } // Client Component — only where interactivity needed "use client"; function AddToCartButton({ productId }: { productId: string }) { const [adding, setAdding] = useState(false); return ; } ``` ## Next.js: config essentials ```js // 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 ```tsx // Generic list component function List({ items, renderItem }: { items: T[]; renderItem: (item: T) => React.ReactNode; }) { return ; } // Props extending HTML element interface ButtonProps extends React.ButtonHTMLAttributes { variant?: "primary" | "ghost" | "danger"; isLoading?: boolean; } export function Button({ variant = "primary", isLoading, children, ...props }: ButtonProps) { return ( ); } ``` ## Performance: bundle analysis Common heavy deps to replace: | Package | Size | Alternative | |---|---|---| | moment | 290KB | `dayjs` (2KB) or `date-fns` (12KB) | | lodash | 71KB | `lodash-es` (tree-shakeable) | | axios | 14KB | native `fetch` or `ky` (3KB) | | @mui/material | Large | shadcn/ui or Radix UI | ```bash # Analyze bundle npx @next/bundle-analyzer # or npx vite-bundle-visualizer ``` ## Accessibility essentials ```tsx // Skip link — place before main nav Skip to main content // Icon button — always label // 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 ```