Patterns

Consistency is the foundation of maintainable code. At Databayt, we follow established patterns that ensure our codebase remains scalable, readable, and collaborative. Every pattern serves a purpose — from atomic components to server actions, from naming conventions to file organization.

We believe in atomic design principles where small, reusable components compose into larger, more complex systems. This isn't just about React components; it's a philosophy that extends to functions, hooks, utilities, and entire application architecture.

The patterns documented here emerge from real-world usage across our open source repositories, tested in production environments, and refined through community feedback. Every decision prioritizes developer experience, type safety, and long-term maintainability.

Naming Conventions

Files and Folders

  • Components: PascalCase for component files (Button.tsx, UserProfile.tsx)
  • Pages: kebab-case for route segments (user-profile, sign-in)
  • Utilities: camelCase for utility files (formatDate.ts, validateEmail.ts)
  • Constants: SCREAMING_SNAKE_CASE for constant files (API_ENDPOINTS.ts)
  • Hooks: camelCase with use prefix (useAuth.ts, useLocalStorage.ts)

Variables and Functions

  • Components: PascalCase (export function UserCard())
  • Functions: camelCase (export function formatCurrency())
  • Variables: camelCase (const userData = await fetchUser())
  • Constants: SCREAMING_SNAKE_CASE (const API_BASE_URL = 'https://api.example.com')
  • Types/Interfaces: PascalCase (interface UserData, type ApiResponse)

Database and API

  • Database tables: snake_case (user_profiles, order_items)
  • API routes: kebab-case (/api/user-profile, /api/order-history)
  • Environment variables: SCREAMING_SNAKE_CASE (DATABASE_URL, NEXT_PUBLIC_API_KEY)

Page & Component Patterns

Page Structure Pattern

We follow a consistent pattern for all page components that ensures predictability and maintainability across the codebase.

Folder Naming Convention

  • Page folders: lowercase (abc/, blog/, user-profile/, dashboard/)

Page Component Pattern

Every page follows this exact structure:

import ComponentContent from "@/components/folder-name/content";

export const metadata = {
  title: "Page Title",
}

export default function PageName() {
  return <ComponentContent />;
}

Examples

ABC Page (src/app/abc/page.tsx):

import AbcContent from "@/components/abc/content";

export const metadata = {
  title: "Abc",
}

export default function Abc() {
  return <AbcContent />;
}

Blog Page (src/app/blog/page.tsx):

import BlogContent from "@/components/blog/content";

export const metadata = {
  title: "Blog",
}

export default function Blog() {
  return <BlogContent />;
}

Component Content Pattern

Each page imports a corresponding content component that follows a specific naming convention.

Content Component Structure

  • Location: src/components/folder-name/content.tsx
  • Naming: {FolderName}Content (PascalCase with "Content" suffix)
  • Export: Default export of the content component

Example Content Component

// src/components/abc/content.tsx
import Hero from "./hero";
import Form from "./form";
import Cards from "./cards";
import Actions from "./actions";

export default function AbcContent() {
  return (
    <main className="flex min-h-screen flex-col">
      <Hero />
      <Form />
      <Cards />
      <Actions />
    </main>
  );
}

Pattern Benefits

  • Predictable Structure: Every page follows the same pattern
  • Easy Navigation: Folder name directly maps to component name
  • Consistent Imports: All content components follow the same import pattern
  • Maintainable: Clear separation between page logic and content
  • Scalable: Easy to add new pages following the established pattern

Function Patterns

Arrow Functions

Preferred for utilities, event handlers, and inline functions. Excellent for maintaining lexical scope and modern JavaScript patterns.

const formatPrice = (amount: number) => {
return new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD'
}).format(amount)
}

Function Declarations

Used for React components, API route handlers, and exported functions. Better for hoisting and debugging stack traces.

export function UserProfile({ userId }: Props) {
const { user, loading } = useUser(userId)

if (loading) return <Skeleton />
return <div>{user.name}</div>
}

Server Actions & Data Fetching

Server Actions Pattern

We use Next.js server actions for form submissions and data mutations, following the "use server" directive pattern:

'use server'

import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'

export async function createUser(formData: FormData) {
  const userData = {
    name: formData.get('name') as string,
    email: formData.get('email') as string,
  }
  
  try {
    await db.user.create({ data: userData })
    revalidatePath('/users')
    redirect('/users')
  } catch (error) {
    throw new Error('Failed to create user')
  }
}

Data Fetching Hierarchy

  • Server Components: Direct database queries for initial data
  • Client Components: React Query/SWR for interactive data
  • Server Actions: Form submissions and mutations
  • API Routes: External integrations and webhooks

Component Architecture

Atomic Design Structure

components/
├── atom/           # Basic elements (Button, Input, Icon)
├── molecule/       # Simple combinations (SearchBox, FormField)
├── organism/       # Complex components (Header, UserTable)
├── template/       # Page layouts and structures
└── page/          # Complete page compositions

Component Composition Pattern

// Compound component pattern
export function Card({ children, className, ...props }: CardProps) {
  return (
    <div className={cn("rounded-lg border bg-card", className)} {...props}>
      {children}
    </div>
  )
}

Card.Header = CardHeader
Card.Content = CardContent
Card.Footer = CardFooter

UI Primitives & Composition

These are our core shadcn/ui primitives and how we compose them across features:

  • Form: Form, Input, Select, Textarea, Checkbox, Radio Group, Button
    • Use Zod + RHF with schema resolvers for validation.
    • Keep forms minimal; prefer multi‑step flows for long inputs.
    • Submit UX: disable button while pending; surface errors near fields and with toast.
  • Table: Table, Dropdown Menu, Badge, Scroll Area
    • Follow our reusable data table pattern. Server pagination/filter/sort for scale.
    • Reference: /docs/table.
  • Overlays: Dialog, Sheet, Drawer, Popover, Tooltip
    • Dialog: short blocking tasks (confirm, create).
    • Sheet: side flows that keep context (edit, filters).
    • Drawer: mobile‑first variant of Sheet.
    • Tooltip/Popover: light, non‑modal context hints and pickers.
  • Navigation: Sidebar, Breadcrumb, Tabs
    • Sidebar for app sections; Tabs for in‑page views; Breadcrumb mirrors URL.
  • Feedback: sonner (Toast), Alert, Skeleton, Progress
    • Always show success/error toasts for mutations; use skeletons for loading.
  • Layout & Media: Card, Separator, Avatar, Badge, Calendar
    • Card organizes content; Separator divides; Avatar/Badge convey identity/state.

Keep compositions small and focused. Prefer composing primitives into atoms, then templates, then blocks.

Modal & Overlay Patterns

  • When to use Dialog vs Sheet
    • Dialog: quick confirmations or short forms (≤ 2 inputs).
    • Sheet: multi‑field or multi‑step flows that benefit from preserving page context.
  • Accessibility
    • All overlays must trap focus, restore focus on close, and be dismissible via Esc.
    • Provide visible labels and keyboard‑reachable actions.

Data Entry Patterns

  • Co‑locate validation.ts (Zod schema) with form.tsx.
  • Infer types from schemas; avoid duplicated interfaces for payloads.
  • Disable submit while pending; show inline field errors and a toast summary on failure.

Navigation Patterns

  • Sidebar sections mirror top‑level routes; hide unavailable items by role.
  • Breadcrumb reflects the current route segments; last segment is non‑clickable.
  • Tabs switch between sibling, same‑entity views (client state); use routes for canonical pages.

Table Patterns

  • Use server‑driven pagination/filter/sort; sync state to URL for shareable views.
  • Columns define id, header, and meta; cell renderers are pure and accessible.
  • Reference the full guide at /docs/table.

TypeScript Patterns

Type Definition Strategy

  • Props: Inline for simple components, separate interfaces for complex ones
  • API Types: Generated from schema or OpenAPI specs
  • Utility Types: Leverage TypeScript's built-in utility types
  • Strict Mode: Always enabled with strict: true in tsconfig

Generic Patterns

// Generic hook pattern
function useApi<T>(endpoint: string): {
  data: T | null
  loading: boolean
  error: Error | null
} {
  // Implementation
}

// Generic component pattern
interface TableProps<T> {
  data: T[]
  columns: Column<T>[]
  onRowClick?: (row: T) => void
}

Generics

We use generics to keep types reusable across entities, tables, actions, and forms. See the dedicated guide: /docs/architecture/generics.

File Organization

Project Structure

src/
├── app/                    # Next.js app router
│   ├── (auth)/            # Route groups
│   ├── api/               # API routes
│   └── globals.css        # Global styles
├── components/            # Reusable components
├── lib/                   # Utilities and configurations
├── hooks/                 # Custom React hooks
├── types/                 # TypeScript type definitions
└── constants/             # Application constants

Import Organization

// 1. React and Next.js imports
import { useState } from 'react'
import { redirect } from 'next/navigation'

// 2. Third-party libraries
import { clsx } from 'clsx'
import { z } from 'zod'

// 3. Internal utilities and types
import { cn } from '@/lib/utils'
import type { User } from '@/types'

// 4. Internal components
import { Button } from '@/components/atom/button'
import { UserCard } from '@/components/molecule/user-card'

Styling Patterns

Tailwind CSS Organization

  • Component styles: Compose utilities with cn() helper
  • Consistent spacing: Use Tailwind's spacing scale
  • Responsive design: Mobile-first responsive patterns
  • Dark mode: CSS variables with Tailwind's dark mode

shadcn/ui Integration

// Extend base components
const buttonVariants = cva(
  "inline-flex items-center justify-center rounded-md text-sm font-medium",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground hover:bg-primary/90",
        destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
      },
      size: {
        default: "h-10 px-4 py-2",
        sm: "h-9 rounded-md px-3",
        lg: "h-11 rounded-md px-8",
      },
    },
  }
)

Error Handling

Graceful Error Boundaries

// Error boundary pattern
export function ErrorBoundary({ children }: { children: React.ReactNode }) {
  return (
    <ErrorBoundaryProvider fallback={<ErrorFallback />}>
      {children}
    </ErrorBoundaryProvider>
  )
}

// Form error handling
export function ContactForm() {
  const [errors, setErrors] = useState<Record<string, string>>({})
  
  const handleSubmit = async (formData: FormData) => {
    try {
      await submitContact(formData)
    } catch (error) {
      if (error instanceof ValidationError) {
        setErrors(error.fieldErrors)
      }
    }
  }
}

Performance Patterns

Optimization Strategies

  • Code splitting: Dynamic imports for large components
  • Image optimization: Next.js Image component with proper sizing
  • Bundle analysis: Regular bundle size monitoring
  • Caching: Strategic use of React Server Components caching

Loading States

// Consistent loading pattern
export function UserProfile({ userId }: { userId: string }) {
  const { data: user, isLoading } = useUser(userId)
  
  if (isLoading) return <UserProfileSkeleton />
  if (!user) return <UserNotFound />
  
  return <UserProfileContent user={user} />
}

Pattern Adoption

These patterns are living guidelines that evolve with our codebase. They're documented in our contributing guide and enforced through ESLint rules, Prettier configuration, and code review processes.

When in doubt, prioritize consistency over personal preference. Our patterns serve the community, ensuring that any developer can jump into any part of the codebase and immediately understand the structure and conventions.

Every pattern has been battle-tested across multiple projects and reflects real-world usage patterns from our open source repositories. They balance developer experience with maintainability, type safety with flexibility.