S
Solo Kit
DocumentationComponentsPricingChangelogRoadmapFAQContact
LoginGet Started
DocumentationComponentsPricing
LoginGet Started
Welcome to Solo Kit DocumentationIntroductionTech StackRoadmapFAQGetting Started
Getting Started

Conventions

Code Conventions

Solo Kit follows consistent conventions across all packages and applications. These conventions ensure code readability, maintainability, and team collaboration.

πŸ“ File Naming Conventions

Routes and Pages

Next.js App Router follows kebab-case:

βœ… Good
app/user-profile/page.tsx
app/api/auth-callback/route.ts
app/admin/user-management/page.tsx

❌ Avoid
app/userProfile/page.tsx
app/user_profile/page.tsx
app/UserProfile/page.tsx

Components

Use PascalCase for all React components:

βœ… Good
components/UserProfile.tsx
components/AuthButton.tsx
components/DashboardLayout.tsx

❌ Avoid
components/userProfile.tsx
components/auth-button.tsx
components/dashboard_layout.tsx

Utilities and Functions

Use camelCase for utilities, hooks, and functions:

βœ… Good
lib/formatDate.ts
hooks/useAuth.ts
lib/validateEmail.ts

❌ Avoid
lib/format-date.ts
hooks/use_auth.ts
lib/ValidateEmail.ts

Constants and Configuration

Use SCREAMING_SNAKE_CASE for constants:

βœ… Good
lib/constants/API_ENDPOINTS.ts
config/DATABASE_CONFIG.ts
lib/constants/ERROR_MESSAGES.ts

❌ Avoid
lib/constants/apiEndpoints.ts
config/database-config.ts
lib/constants/errorMessages.ts

TypeScript Types and Interfaces

Use PascalCase with descriptive names:

βœ… Good
interface User {
  id: string;
  name: string;
  email: string;
}

type AuthState = 'authenticated' | 'unauthenticated' | 'loading';

interface CreateUserRequest {
  name: string;
  email: string;
}

❌ Avoid
interface user { ... }
type authState = string;
interface createUserReq { ... }

πŸ—οΈ Directory Structure Conventions

Feature-Based Organization

Group by features, not by file types:

βœ… Good - Feature-based
components/features/
β”œβ”€β”€ auth/
β”‚   β”œβ”€β”€ LoginForm.tsx
β”‚   β”œβ”€β”€ SignupForm.tsx
β”‚   └── AuthButton.tsx
β”œβ”€β”€ dashboard/
β”‚   β”œβ”€β”€ DashboardStats.tsx
β”‚   β”œβ”€β”€ DashboardLayout.tsx
β”‚   └── StatsCard.tsx
└── profile/
    β”œβ”€β”€ UserProfile.tsx
    β”œβ”€β”€ ProfileSettings.tsx
    └── AvatarUpload.tsx

❌ Avoid - File-type based
components/
β”œβ”€β”€ forms/
β”‚   β”œβ”€β”€ LoginForm.tsx
β”‚   β”œβ”€β”€ SignupForm.tsx
β”‚   └── ProfileForm.tsx
β”œβ”€β”€ buttons/
β”‚   β”œβ”€β”€ AuthButton.tsx
β”‚   β”œβ”€β”€ SubmitButton.tsx
β”‚   └── DeleteButton.tsx
└── layouts/
    β”œβ”€β”€ DashboardLayout.tsx
    └── ProfileLayout.tsx

Component Colocation

Keep related files together:

βœ… Good
components/features/dashboard/
β”œβ”€β”€ DashboardStats.tsx
β”œβ”€β”€ DashboardStats.test.tsx
β”œβ”€β”€ DashboardStats.stories.tsx
└── dashboard-stats.module.css

❌ Avoid
components/DashboardStats.tsx
__tests__/DashboardStats.test.tsx
stories/DashboardStats.stories.tsx
styles/dashboard-stats.css

πŸ“¦ Import/Export Conventions

Import Order

Organize imports in this specific order:

βœ… Good
// 1. External libraries
import React from 'react';
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';

// 2. Internal packages (monorepo)
import { Button } from '@packages/ui';
import { formatDate } from '@packages/utils';
import { db } from '@packages/database';

// 3. Local app imports (absolute paths)
import { auth } from '@/lib/auth';
import { validateUser } from '@/lib/validation';
import { UserCard } from '@/components/features/user/UserCard';

// 4. Relative imports (last resort)
import { helper } from './utils';
import './styles.css';

❌ Avoid
import { helper } from './utils';
import React from 'react';
import { Button } from '@packages/ui';
import { auth } from '@/lib/auth';

Barrel Exports

Use index.ts files for clean imports:

// packages/ui/src/index.ts
export { Button } from './components/button';
export { Card } from './components/card';
export { Input } from './components/input';
export { Modal } from './components/modal';

// Usage
import { Button, Card, Input } from '@packages/ui';

Named vs Default Exports

Prefer named exports for better tree-shaking:

βœ… Good - Named exports
// components/UserProfile.tsx
export function UserProfile({ user }: UserProfileProps) {
  return <div>{user.name}</div>;
}

// Usage
import { UserProfile } from '@/components/UserProfile';

βœ… Acceptable - Default exports for pages
// app/profile/page.tsx
export default function ProfilePage() {
  return <UserProfile user={currentUser} />;
}

🎯 Component Conventions

Component Structure

Follow this component template:

// 1. Imports (following import order)
import React from 'react';
import { Button } from '@packages/ui';
import { cn } from '@/lib/utils';

// 2. Types and interfaces
interface UserCardProps {
  user: {
    id: string;
    name: string;
    email: string;
    avatar?: string;
  };
  className?: string;
  onEdit?: () => void;
}

// 3. Component implementation
export function UserCard({ user, className, onEdit }: UserCardProps) {
  return (
    <div className={cn('rounded-lg border p-4', className)}>
      <div className="flex items-center space-x-3">
        {user.avatar && (
          <img
            src={user.avatar}
            alt={user.name}
            className="h-10 w-10 rounded-full"
          />
        )}
        <div>
          <h3 className="font-semibold">{user.name}</h3>
          <p className="text-sm text-muted-foreground">{user.email}</p>
        </div>
      </div>
      {onEdit && (
        <Button onClick={onEdit} className="mt-3">
          Edit Profile
        </Button>
      )}
    </div>
  );
}

// 4. Default props (if needed)
UserCard.defaultProps = {
  className: '',
};

Component Props

Use descriptive prop names:

βœ… Good
interface ButtonProps {
  children: React.ReactNode;
  variant?: 'primary' | 'secondary' | 'destructive';
  size?: 'sm' | 'md' | 'lg';
  isLoading?: boolean;
  onClick?: () => void;
}

❌ Avoid
interface ButtonProps {
  children: React.ReactNode;
  type?: string;
  big?: boolean;
  loading?: boolean;
  click?: () => void;
}

Server vs Client Components

Be explicit about component type:

// Server Component (default - no directive)
import { db } from '@packages/database';

export async function UserList() {
  const users = await db.select().from(usersTable);
  
  return (
    <div>
      {users.map(user => (
        <UserCard key={user.id} user={user} />
      ))}
    </div>
  );
}

// Client Component (explicit directive)
'use client';

import { useState } from 'react';
import { Button } from '@packages/ui';

export function InteractiveButton() {
  const [count, setCount] = useState(0);
  
  return (
    <Button onClick={() => setCount(c => c + 1)}>
      Clicked {count} times
    </Button>
  );
}

πŸ”§ TypeScript Conventions

Type Definitions

Place types close to usage:

// βœ… Good - Colocated types
// components/features/auth/LoginForm.tsx
interface LoginFormProps {
  onSuccess?: () => void;
  redirectTo?: string;
}

interface LoginFormData {
  email: string;
  password: string;
  rememberMe?: boolean;
}

export function LoginForm({ onSuccess, redirectTo }: LoginFormProps) {
  // Component implementation
}

// βœ… Good - Shared types in separate file
// lib/types/auth.types.ts
export interface User {
  id: string;
  email: string;
  name: string;
  role: 'user' | 'admin';
}

export interface AuthSession {
  user: User;
  token: string;
  expiresAt: Date;
}

Utility Types

Use TypeScript utility types appropriately:

// User entity
interface User {
  id: string;
  email: string;
  name: string;
  createdAt: Date;
  updatedAt: Date;
}

// API types using utility types
type CreateUserRequest = Omit<User, 'id' | 'createdAt' | 'updatedAt'>;
type UpdateUserRequest = Partial<Pick<User, 'name' | 'email'>>;
type UserResponse = Pick<User, 'id' | 'email' | 'name'>;

Generics

Use descriptive generic names:

βœ… Good
interface ApiResponse<TData = unknown> {
  data: TData;
  success: boolean;
  message: string;
}

interface Repository<TEntity, TCreateInput, TUpdateInput> {
  create(input: TCreateInput): Promise<TEntity>;
  update(id: string, input: TUpdateInput): Promise<TEntity>;
  findById(id: string): Promise<TEntity | null>;
}

❌ Avoid
interface ApiResponse<T = any> {
  data: T;
  success: boolean;
  message: string;
}

🎨 Styling Conventions

Tailwind CSS Classes

Order Tailwind classes logically:

βœ… Good - Logical grouping
<div className="
  flex items-center justify-between
  w-full h-12 px-4 py-2
  bg-white border border-gray-200 rounded-lg
  text-sm font-medium text-gray-900
  hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500
  transition-colors duration-200
">

❌ Avoid - Random order
<div className="text-sm bg-white focus:ring-2 px-4 flex hover:bg-gray-50 border items-center w-full rounded-lg">

Class order pattern:

  1. Layout: flex, grid, block, inline
  2. Positioning: relative, absolute, top-0
  3. Sizing: w-full, h-12, min-h-screen
  4. Spacing: p-4, m-2, space-x-2
  5. Background: bg-white, bg-gradient-to-r
  6. Border: border, border-gray-200, rounded-lg
  7. Typography: text-sm, font-medium, text-gray-900
  8. Interactive: hover:, focus:, active:
  9. Transitions: transition-colors, duration-200

Component Variants

Use consistent variant patterns:

interface ButtonProps {
  variant?: 'primary' | 'secondary' | 'destructive' | 'ghost' | 'outline';
  size?: 'sm' | 'md' | 'lg';
}

const buttonVariants = {
  variant: {
    primary: 'bg-blue-600 text-white hover:bg-blue-700',
    secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200',
    destructive: 'bg-red-600 text-white hover:bg-red-700',
    ghost: 'hover:bg-gray-100 text-gray-900',
    outline: 'border border-gray-300 bg-white hover:bg-gray-50',
  },
  size: {
    sm: 'px-2 py-1 text-xs',
    md: 'px-4 py-2 text-sm',
    lg: 'px-6 py-3 text-base',
  },
};

πŸ—‚οΈ Database Conventions

Schema Naming

Use consistent table and column naming:

// βœ… Good - Snake case for database
export const users = pgTable('users', {
  id: uuid('id').primaryKey().defaultRandom(),
  email: text('email').notNull().unique(),
  firstName: text('first_name').notNull(),
  lastName: text('last_name').notNull(),
  createdAt: timestamp('created_at').defaultNow().notNull(),
  updatedAt: timestamp('updated_at').defaultNow().notNull(),
});

export const userSessions = pgTable('user_sessions', {
  id: uuid('id').primaryKey().defaultRandom(),
  userId: uuid('user_id').references(() => users.id).notNull(),
  token: text('token').notNull().unique(),
  expiresAt: timestamp('expires_at').notNull(),
});

❌ Avoid - Mixed naming conventions
export const Users = pgTable('Users', {
  ID: uuid('ID').primaryKey(),
  Email: text('Email').notNull(),
  firstName: text('firstName').notNull(),
  // Inconsistent naming
});

Query Conventions

Use descriptive function names:

// βœ… Good - Descriptive names
export async function getUserById(userId: string) {
  return db.select().from(users).where(eq(users.id, userId));
}

export async function createUser(userData: CreateUserData) {
  return db.insert(users).values(userData).returning();
}

export async function updateUserEmail(userId: string, email: string) {
  return db
    .update(users)
    .set({ email, updatedAt: new Date() })
    .where(eq(users.id, userId));
}

❌ Avoid - Generic names
export async function getUser(id: string) { ... }
export async function createUser(data: any) { ... }
export async function updateUser(id: string, data: any) { ... }

πŸ”„ API Conventions

Server Actions

Use descriptive action names:

// βœ… Good - Clear action purpose
export const updateUserProfile = actionClientUser
  .metadata({ name: 'updateUserProfile' })
  .schema(updateUserProfileSchema)
  .action(async ({ ctx: { userId }, parsedInput }) => {
    // Implementation
  });

export const deleteUserAccount = actionClientUser
  .metadata({ name: 'deleteUserAccount' })
  .schema(deleteUserAccountSchema)
  .action(async ({ ctx: { userId } }) => {
    // Implementation
  });

❌ Avoid - Generic names
export const userAction = actionClientUser.schema(schema).action(...);
export const doUserStuff = actionClientUser.schema(schema).action(...);

API Routes

Use RESTful conventions:

// βœ… Good - RESTful naming
// app/api/users/route.ts - GET /api/users (list users)
// app/api/users/[id]/route.ts - GET /api/users/123 (get user)
// app/api/users/[id]/profile/route.ts - PATCH /api/users/123/profile

// Route handlers
export async function GET(request: NextRequest) {
  // Get users
}

export async function POST(request: NextRequest) {
  // Create user
}

export async function PATCH(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  // Update user
}

❌ Avoid - Non-RESTful naming
// app/api/getUserById/route.ts
// app/api/createUser/route.ts
// app/api/updateUserData/route.ts

πŸ“‹ Error Handling Conventions

Error Types

Use structured error handling:

// lib/errors.ts
export class ValidationError extends Error {
  constructor(
    message: string,
    public field: string,
    public code: string = 'VALIDATION_ERROR'
  ) {
    super(message);
    this.name = 'ValidationError';
  }
}

export class DatabaseError extends Error {
  constructor(
    message: string,
    public operation: string,
    public code: string = 'DATABASE_ERROR'
  ) {
    super(message);
    this.name = 'DatabaseError';
  }
}

// Usage
if (!email) {
  throw new ValidationError('Email is required', 'email', 'REQUIRED_FIELD');
}

Error Boundaries

Use consistent error boundary patterns:

// app/error.tsx
'use client';

interface ErrorPageProps {
  error: Error & { digest?: string };
  reset: () => void;
}

export default function Error({ error, reset }: ErrorPageProps) {
  return (
    <div className="flex flex-col items-center justify-center min-h-[400px]">
      <h2 className="text-lg font-semibold text-gray-900 mb-2">
        Something went wrong!
      </h2>
      <p className="text-sm text-gray-600 mb-4">
        {error.message || 'An unexpected error occurred'}
      </p>
      <button
        onClick={() => reset()}
        className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700"
      >
        Try again
      </button>
    </div>
  );
}

πŸ“– Documentation Conventions

JSDoc Comments

Document all exported functions:

/**
 * Formats a date according to the user's locale preferences
 * @param date - The date to format
 * @param locale - Optional locale override (defaults to 'en-US')
 * @param options - Intl.DateTimeFormat options
 * @returns Formatted date string
 * 
 * @example
 * ```typescript
 * formatDate(new Date(), 'en-US', { dateStyle: 'medium' })
 * // Returns: "Jan 15, 2024"
 * ```
 */
export function formatDate(
  date: Date,
  locale: string = 'en-US',
  options: Intl.DateTimeFormatOptions = {}
): string {
  return new Intl.DateTimeFormat(locale, options).format(date);
}

Component Documentation

Document component props and usage:

/**
 * UserCard displays user information in a card format
 * 
 * @example
 * ```tsx
 * <UserCard 
 *   user={{ id: '1', name: 'John', email: 'john@example.com' }}
 *   onEdit={() => console.log('Edit clicked')}
 * />
 * ```
 */
interface UserCardProps {
  /** User object containing id, name, email, and optional avatar */
  user: {
    id: string;
    name: string;
    email: string;
    avatar?: string;
  };
  /** Additional CSS classes */
  className?: string;
  /** Callback fired when edit button is clicked */
  onEdit?: () => void;
}

export function UserCard({ user, className, onEdit }: UserCardProps) {
  // Implementation
}

🎯 Code Quality Conventions

Code Review Guidelines

Before submitting code:

  • Follow naming conventions
  • Add TypeScript types
  • Include error handling
  • Add JSDoc for exported functions
  • Test responsive design
  • Verify accessibility
  • Check performance impact

Linting and Formatting

Use automated tools:

// .eslintrc.js
module.exports = {
  extends: ['@packages/eslint-config/next'],
  rules: {
    // Naming conventions
    'camelcase': ['error', { 'properties': 'never' }],
    '@typescript-eslint/naming-convention': [
      'error',
      {
        'selector': 'interface',
        'format': ['PascalCase'],
        'custom': {
          'regex': '^I[A-Z]',
          'match': false
        }
      }
    ]
  }
};

πŸš€ Next Steps

With consistent conventions in place:

  1. Common Commands - Master the development commands
  2. Troubleshooting - Solve common issues
  3. Database Guide - Deep dive into database patterns
  4. API Development - Build robust APIs

These conventions ensure your Solo Kit codebase remains maintainable, scalable, and collaborative as your team grows.

On this page

Code ConventionsπŸ“ File Naming ConventionsRoutes and PagesComponentsUtilities and FunctionsConstants and ConfigurationTypeScript Types and InterfacesπŸ—οΈ Directory Structure ConventionsFeature-Based OrganizationComponent ColocationπŸ“¦ Import/Export ConventionsImport OrderBarrel ExportsNamed vs Default Exports🎯 Component ConventionsComponent StructureComponent PropsServer vs Client ComponentsπŸ”§ TypeScript ConventionsType DefinitionsUtility TypesGenerics🎨 Styling ConventionsTailwind CSS ClassesComponent VariantsπŸ—‚οΈ Database ConventionsSchema NamingQuery ConventionsπŸ”„ API ConventionsServer ActionsAPI RoutesπŸ“‹ Error Handling ConventionsError TypesError BoundariesπŸ“– Documentation ConventionsJSDoc CommentsComponent Documentation🎯 Code Quality ConventionsCode Review GuidelinesLinting and FormattingπŸš€ Next Steps