This document outlines coding standards, naming conventions, and best practices used throughout the monorepo.
Prefer interfaces for objects:
interface User {
id: string;
email: string;
firstName: string;
}
// Use for props, API responses, data structures
Use type aliases for unions/intersections:
type ItemStatus = 'ACTIVE' | 'SOLD' | 'REMOVED';
type UserWithToken = User & { token: string };
File naming:
types.ts or {feature}.types.tssrc/features/auth/types.ts)any — Use unknown and type guards if neededFunctional components only:
export function MyComponent({ prop1, prop2 }: MyComponentProps) {
// Hooks
const [state, setState] = useState();
// Effects
useEffect(() => {}, []);
// Handlers
const handleClick = () => {};
// Render
return <div>...</div>;
}
Component naming:
ItemCard.tsx, UserProfile.tsxButton.tsx, ButtonIcon.tsxFile organization:
// 1. Imports (external, then internal)
import { useState } from 'react';
import { Button } from '@/components/ui';
import { useAuth } from '@/src/features/auth/useAuth';
// 2. Types
interface MyComponentProps {
title: string;
}
// 3. Component
export function MyComponent({ title }: MyComponentProps) {
// ...
}
// 4. Sub-components (if needed)
function SubComponent() {
// ...
}
Naming:
use: useAuth, useImageProcessoruseCreateContainer not useCreateCustom hook structure:
export function useMyHook(dependency: string) {
const [state, setState] = useState<State>(initialState);
useEffect(() => {
// Side effects
}, [dependency]);
const action = useCallback(() => {
// Action logic
}, []);
return { state, action };
}
Rules:
useComponents: PascalCase.tsx
Button.tsx, ItemCard.tsx, UserProfile.tsxUtilities: camelCase.ts
utils.ts, processImage.ts, validateInput.tsHooks: camelCase.ts with use prefix
useAuth.ts, useImageProcessor.tsTypes: camelCase.types.ts or types.ts
image.types.ts, auth.types.tsConstants: camelCase.ts
messages.ts, categories.tsComponents: kebab-case or camelCase
discart-me/, qrack/, comments/Features: kebab-case
image-processing/, user-auth/Apps: kebab-case
discart-me/, qrack/@/src, @/components)Example:
// 1. React/Next.js
import { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation';
// 2. External
import clsx from 'clsx';
import { toast } from 'sonner';
// 3. Internal shared
import { Button } from '@/components/ui';
import { useAuth } from '@/src/features/auth/useAuth';
import { processImage } from '@/src/lib/image';
// 4. Relative
import { MySubComponent } from './MySubComponent';
import { styles } from './styles.module.css';
// 5. Types (if separate)
import type { User } from '@/src/types';
Always use path aliases for shared code:
// ✅ Good
import { Button } from '@/components/ui';
import { useAuth } from '@/src/features/auth/useAuth';
// ❌ Avoid
import { Button } from '../../../components/ui';
Relative imports for same feature:
// ✅ Good (same feature)
import { SubComponent } from './SubComponent';
// ✅ Good (shared code)
import { Button } from '@/components/ui';
Structure:
features/
my-feature/
├── components/ # Feature-specific UI
│ └── MyComponent.tsx
├── hooks/ # Feature-specific logic
│ └── useMyFeature.ts
├── types.ts # Feature types
└── index.ts # Public exports (optional)
Guidelines:
Location: src/lib/{category}/
Structure:
lib/
image/
├── processImage.ts # Main function
├── utils.ts # Helpers
├── constants.ts # Constants
├── types.ts # Types
└── index.ts # Public exports
Guidelines:
index.ts for clean importsPrefer utility classes:
// ✅ Good
<div className="flex items-center gap-4 p-6 bg-zinc-900 rounded-xl">
<Button variant="primary">Click</Button>
</div>
// ❌ Avoid inline styles
<div style=>
Extract to components for reusability:
// ✅ Good (reusable)
<Card>
<Button variant="primary">Click</Button>
</Card>
// ❌ Avoid (repeated utilities)
<div className="p-6 bg-zinc-900 rounded-xl border border-zinc-800">
<Button variant="primary">Click</Button>
</div>
Responsive design:
// ✅ Good
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{items.map(item => <ItemCard key={item.id} item={item} />)}
</div>
Use clsx for conditional classes:
import clsx from 'clsx';
<button
className={clsx(
'px-4 py-2 rounded-lg',
isActive && 'bg-blue-500 text-white',
isDisabled && 'opacity-50 cursor-not-allowed'
)}
>
Click
</button>
Use tailwind-merge when merging classes:
import { cn } from '@/src/lib/utils'; // wrapper around tailwind-merge
<Button className={cn('px-6', props.className)}>
Click
</Button>
REST APIs:
getItems() — Fetch listgetItemById(id) — Fetch singlecreateItem(data) — CreateupdateItem(id, data) — UpdatedeleteItem(id) — DeleteGraphQL:
getContainers() — QuerycreateContainer(input) — MutationAlways handle errors:
try {
const items = await getItems();
setItems(items);
} catch (error) {
console.error('Failed to fetch items:', error);
toast.error('Could not load items. Please try again.');
}
Error messages:
src/constants/messages.ts)Use useState for component state:
const [isOpen, setIsOpen] = useState(false);
const [items, setItems] = useState<Item[]>([]);
Use useReducer for complex state (if needed):
const [state, dispatch] = useReducer(reducer, initialState);
Use Context for app-wide state:
// Auth, theme, user preferences
const { user, login, logout } = useAuth();
Use React Query for server state:
const { data, isLoading, error } = useQuery({
queryKey: ['items'],
queryFn: () => getItems(),
});
Explain “why”, not “what”:
// ❌ Bad (obvious)
// Set isOpen to true
setIsOpen(true);
// ✅ Good (explains why)
// Auto-close modal after 3 seconds to prevent accidental submissions
setTimeout(() => setIsOpen(false), 3000);
JSDoc for public APIs:
/**
* Process and compress an image file
* @param file - Input image file
* @param options - Processing options (maxWidth, quality, etc.)
* @returns Processed image with metadata
*/
export function processImage(file: File, options: ImageProcessingOptions): Promise<ProcessedImage> {
// ...
}
Required for:
app/{app}/README.md)src/features/{feature}/README.md - optional)src/lib/{library}/README.md - optional)Should include:
Location: __tests__/ or .test.ts files
Naming: {file}.test.ts
Button.test.tsxprocessImage.test.tsStructure:
describe('processImage', () => {
it('should resize image to max width', async () => {
// Test
});
it('should compress image to target size', async () => {
// Test
});
});
Format: type(scope): message
Types:
feat: New featurefix: Bug fixdocs: Documentationrefactor: Code refactoringstyle: Formattingtest: Testschore: MaintenanceExamples:
feat(qrack): add QR code scanner
fix(discart-me): image upload error handling
docs: update architecture documentation
refactor(auth): consolidate auth providers
Format: {type}/{description}
Examples:
feat/add-image-processingfix/upload-error-handlingdocs/update-conventionsUse semantic elements:
// ✅ Good
<button onClick={handleClick}>Submit</button>
<nav><ul><li><a href="/">Home</a></li></ul></nav>
// ❌ Avoid
<div onClick={handleClick}>Submit</div>
<div><div><div>Home</div></div></div>
Use when needed:
<button
aria-label="Close dialog"
aria-expanded={isOpen}
onClick={handleClose}
>
×
</button>
Use Headless UI for complex components (has ARIA built-in)
Dynamic imports for heavy dependencies:
const QRCodeScanner = dynamic(() => import('./QRCodeScanner'), {
ssr: false,
});
Use useMemo for expensive computations:
const filteredItems = useMemo(() => {
return items.filter(item => item.status === 'ACTIVE');
}, [items]);
Use useCallback for stable function references:
const handleClick = useCallback(() => {
doSomething(id);
}, [id]);