| topic | components | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| last_verified | 2026-06-23 | |||||||||
| sources |
|
All components are Server Components unless they have a "use client" directive.
Server Components can be async, fetch data directly, and access server-only resources.
Only when you need:
useState,useReducer,useContext, or other React hooksuseEffector lifecycle behavior- Browser APIs (
window,document,localStorage) - Event handlers (
onClick,onChange, etc.) on the component itself
Do not add "use client" to layouts, pages, or wrapper components just because a child needs it — push "use client" down to the smallest possible component.
components/
ui/ — shadcn primitives (avatar, badge, button, card, dialog, dropdown-menu,
input, label, separator, skeleton, sonner)
common/ — cross-feature reusable UI (container, h1, h2, h3, loader)
home/ — feature-scoped components (hero, about)
layout/ — layout wrappers (navbar, footer, etc.)
lib/
utils.ts — cn() utility and other non-React helpers
types/ — shared TypeScript type definitions
Route files (page.tsx, layout.tsx, loading.tsx, error.tsx) live in app/ only.
All conditional class merging uses cn() from lib/utils.ts:
// lib/utils.ts
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}Import it as import { cn } from "@/lib/utils" in every component that merges classes.
Generated by shadcn/ui. Use class-variance-authority (cva) for variant logic and Slot from radix-ui for the asChild pattern. Do not hand-edit these files directly; re-run the shadcn CLI to update them.
Pattern from button.tsx:
import { cva, type VariantProps } from "class-variance-authority"
import { Slot } from "radix-ui"
import { cn } from "@/lib/utils"
const buttonVariants = cva("<base-classes>", {
variants: {
variant: { default: "...", outline: "...", secondary: "...", ghost: "...", destructive: "...", link: "..." },
size: { default: "...", xs: "...", sm: "...", lg: "...", icon: "...", "icon-xs": "...", "icon-sm": "...", "icon-lg": "..." },
},
defaultVariants: { variant: "default", size: "default" },
})
function Button({
className,
variant = "default",
size = "default",
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot.Root : "button"
return (
<Comp
data-slot="button"
data-variant={variant}
data-size={size}
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
)
}
export { Button, buttonVariants }These are hand-written and use cn() with Tailwind classes directly. They do not use cva.
Container — constrains max width and centers content:
const Container = ({ children, className }: { children: React.ReactNode; className?: string }) => (
<div className={cn("mx-auto max-w-7xl", className)}>{children}</div>
)H1, H2, H3 — semantic heading wrappers that forward a className prop through cn(). H2 makes className optional; H1 and H3 require it.
Loader — accepts a required className prop and renders a div with those classes applied via cn().
Components specific to the home feature. Scoped to avoid polluting common/.
- No
any. Use proper interfaces orunknown. - For shadcn primitives, extend
React.ComponentProps<"element">(notButtonHTMLAttributes) and combine withVariantProps<typeof variantsFn>. - For common/feature components, inline prop types in the same file unless the type is shared across multiple files — then move it to
types/. - Import types with
import typeto keep runtime bundles clean. - React 19: JSX transform is automatic — no
import React from "react"needed unless you referenceReact.*directly.
Use TypeScript path aliases (@/) from tsconfig.json. Do not write deep relative import chains.