Component Anatomy
The consistent internal structure every component follows — imports, types, constants, function, export.
Principle
When every component follows the same structure, you stop thinking about where things go inside a file and start thinking about what the component actually does. Consistent anatomy means anyone on the team can open any file and immediately know where to look — props are always at the top, constants are always before the function, exports are always at the bottom.
The hardest part of component anatomy is constants vs. props. Rule of thumb: if it never changes based on what's passed in, it is a constant. If it could change from outside, it is a prop.
Rules
- check_circleImports firstOrder: React → external libraries → internal aliases (@/) → relative imports (./). This makes dependencies visible at a glance.
- check_circleTypes and interfaces secondDefine all types used in this file immediately after imports. Props interface always comes first.
- check_circleConstants thirdComponent-scoped constants (static data, config, labels) come before the function. Never define constants inside the function body.
- check_circleComponent function fourthThe function itself comes after everything it depends on. Keep it focused — if it grows past 200 lines, split it.
- check_circleNamed export lastAlways use named exports, never default exports. Named exports make refactoring and search easier.
Pattern
// 1. IMPORTS — React → external → internal → relative import { useState } from 'react'; import Link from 'next/link'; import { cn } from '@/shared/utils/cn'; import { useSavedStore } from '../stores/useSavedStore'; // 2. TYPES interface RecipeCardProps { slug: string; title: string; description: string; category: string; } // 3. CONSTANTS — static, never changes based on props const MAX_DESCRIPTION_LENGTH = 120; const CARD_BASE_CLASS = 'rounded-xl border bg-white shadow-sm'; // 4. COMPONENT FUNCTION export function RecipeCard({ slug, title, description, category }: RecipeCardProps) { const [isHovered, setIsHovered] = useState(false); const { isSaved } = useSavedStore(); const truncated = description.slice(0, MAX_DESCRIPTION_LENGTH); return ( <div className={cn(CARD_BASE_CLASS, isHovered && 'shadow-lg')}> {/* ... */} </div> ); } // 5. EXPORT — named, at the bottom // (already exported above with "export function")
Implementation
Version Compatibility
Requires React 19+ and the latest stable versions of all dependencies shown.
In Next.js, mark client components explicitly with 'use client' — it goes above all imports as the very first line.
// 'use client' goes ABOVE imports if the component is a Client Component 'use client'; // 1. IMPORTS import { useState } from 'react'; import Link from 'next/link'; import { cn } from '@/shared/utils/cn'; // 2. TYPES interface RecipeCardProps { slug: string; title: string; framework: 'nextjs' | 'vitejs'; } // 3. CONSTANTS const HREF_MAP = { nextjs: '/nextjs/cookbook', vitejs: '/vitejs/cookbook', } as const; // 4. COMPONENT export function RecipeCard({ slug, title, framework }: RecipeCardProps) { const [saved, setSaved] = useState(false); const href = `${HREF_MAP[framework]}/${slug}`; return ( <Link href={href}> <span>{title}</span> </Link> ); }