Dev Playbook
Conventions

Next.js Conventions

Next.js and React specific patterns and standards.

Architecture: App Router

App Router only — no pages/ directory.

src/
├── app/
│   ├── [locale]/
│   │   ├── (portal-student)/
│   │   ├── (portal-instructor)/
│   │   ├── (portal-admin)/
│   │   ├── (auth)/
│   │   ├── layout.tsx
│   │   └── page.tsx
│   ├── api/            ← Route handlers (if needed)
│   └── globals.css
├── components/
│   ├── ui/             ← shadcn/ui primitives
│   ├── shared/         ← Reusable across portals
│   └── features/       ← Feature-specific components
├── lib/
│   ├── api/            ← API client functions
│   ├── hooks/          ← Custom React hooks
│   ├── stores/         ← Zustand stores
│   └── utils/          ← Pure utility functions
├── types/              ← TypeScript type definitions
└── messages/           ← i18n translation files (next-intl)

Component Rules

  1. Server Components by default — Only add "use client" when you need interactivity, browser APIs, or hooks
  2. Colocate — Keep related files together (component + its types + its styles)
  3. One component per file — Exception: small helper components used only by the parent
  4. Export named, not defaultexport function CourseCard() not export default function
    • Exception: App Router route files (page.tsx, layout.tsx, loading.tsx, error.tsx, not-found.tsx) require default exports per Next.js convention

State Management

TypeToolWhen
Server stateTanStack QueryAPI data fetching, caching, mutations
Client stateZustandUI state (sidebar open, theme, filters)
Form stateReact Hook Form + ZodAll forms, always validated
URL stateuseSearchParamsFilters, pagination, sorting

Never use React Context for state that changes frequently. Zustand or URL state instead.

Naming

ElementConventionExample
Fileskebab-casecourse-card.tsx
ComponentsPascalCaseCourseCard
HookscamelCase with use prefixuseCourseData
StorescamelCase with use + StoreuseSidebarStore
Types/InterfacesPascalCaseCourseResponse
ConstantsSCREAMING_SNAKEMAX_FILE_SIZE
CSS classesTailwind utilities

Styling

  • Tailwind CSS for everything — no CSS modules, no styled-components
  • shadcn/ui for base components — customize via Tailwind, don't fork
  • cn() helper for conditional classes (from lib/utils)
  • No inline styles — Use Tailwind classes

i18n (Internationalization)

  • next-intl for all user-facing text
  • Never hardcode UI text — always use t('key')
  • Translation files: messages/tr.json, messages/en.json
  • Default locale: Turkish (tr)

Data Fetching

// Server Component — fetch directly
async function CoursePage({ params }: { params: { id: string } }) {
  const course = await getCourse(params.id);
  return <CourseDetail course={course} />;
}

// Client Component — TanStack Query
function CourseList() {
  const { data, isLoading } = useQuery({
    queryKey: ['courses'],
    queryFn: () => apiClient.getCourses(),
  });
}

Forms

Always: React Hook Form + Zod schema

const schema = z.object({
  title: z.string().min(1, 'Required').max(200),
  description: z.string().optional(),
});

type FormData = z.infer<typeof schema>;

Testing

  • Vitest for unit tests
  • Playwright for E2E tests
  • Test user interactions, not implementation details
  • Mock API calls, not components

On this page