Project Structure

The boilerplate follows a carefully designed project structure that promotes maintainability, scalability, and developer productivity. The structure enforces clear boundaries between different concerns and provides consistent patterns across the entire codebase.
Perfect for: Understanding the codebase organization, following established patterns, and maintaining consistency as your application grows.

Project Overview

ShipSaaS is a monolithic Next.js SaaS boilerplate with a well-organized layered architecture. It emphasizes clear separation of concerns with dedicated layers for routing, components, services, and database access.
Project Root Organization:
shipsaas-ai-boilerplate/
├── src/                            # 📁 Application source code
├── public/                         # 📁 Static assets (images, fonts)
├── docs/                           # 📚 Documentation
├── messages/                       # 🌐 i18n translation files
├── scripts/                        # 🔧 Build/database scripts
├── drizzle/                        # 🗄️ Database migrations
├── e2e/                           # 🧪 End-to-end tests
├── .github/                       # 📋 GitHub workflows
├── .claude/                       # 🤖 Claude Code configuration
├── .cursor/                       # 📋 Cursor IDE rules
├── package.json                   # Dependencies configuration
├── next.config.ts                 # Next.js configuration
├── tsconfig.json                  # TypeScript configuration
├── drizzle.config.ts              # Drizzle ORM configuration
└── playwright.config.ts           # E2E testing configuration
Key Principles:
  • Single application - All code in one cohesive Next.js project
  • Layered architecture - Clear separation: routes → components → services → database
  • Feature-based organization - Components grouped by feature
  • Service layer pattern - Business logic isolated in services
  • Type safety - Full TypeScript coverage with Drizzle ORM

Naming Conventions

Consistent naming patterns across the codebase improve readability and maintainability:
File Naming Patterns (ShipSaaS Standard):
✅ GOOD FILE NAMES:

# Components (PascalCase - React convention)
UserProfile.tsx
SubscriptionCard.tsx
PaymentForm.tsx
UserAvatar.tsx
NotificationBell.tsx

# Pages & Layouts (Next.js conventions)
page.tsx
layout.tsx
loading.tsx
error.tsx
not-found.tsx

# Services (kebab-case with suffix)
user-service.ts
subscription-service.ts
email-service.ts
organization-service.ts

# Service Facades (kebab-case with suffix)
user-service-facade.ts
subscription-service-facade.ts
email-service-facade.ts

# Database Models (kebab-case)
user.ts                           # Schema definition
organization.ts
subscription.ts

# Repositories (kebab-case with suffix)
user-repository.ts
organization-repository.ts
subscription-repository.ts

# Utilities & Helpers (kebab-case)
user-helper.ts
date-helper.ts
api-client.ts
common-helper.ts

# Validation Schemas (kebab-case with suffix)
user-validation.ts
subscription-validation.ts

# Context & Hooks (camelCase for hooks, PascalCase for Context)
useUserData.ts
useSubscription.ts
UserContext.tsx
ThemeContext.tsx
Directory Naming:
✅ GOOD DIRECTORY NAMES:

# Feature directories (kebab-case)
/user/
/organization/
/subscription/
/payment/
/api-key/

# Component feature directories (kebab-case)
/checkout-stripe/
/user-profile/
/admin-dashboard/
/project-management/

# Service subdirectories (kebab-case)
/authentication/
/authorization/
/validation/
/errors/

# Next.js special directories (exact names)
/(public)/
/(auth)/
/(app)/
/admin/
/api/
File Organization Rules:
  • PascalCase: React components (.tsx files)
  • kebab-case: Services, utilities, hooks, repositories, models
  • camelCase: React hooks (useMyHook.ts)
  • lowercase: Next.js special files (page.tsx, layout.tsx, etc.)
  • SCREAMING_SNAKE_CASE: Constants only

Import Organization

Consistent import patterns improve code readability and dependency tracking:
Import Order and Grouping (ShipSaaS Standard):
// ✅ GOOD: Organized import groups
// 1. Node.js built-ins
import fs from 'fs'
import path from 'path'

// 2. External libraries
import React from 'react'
import { z } from 'zod'
import { eq } from 'drizzle-orm'

// 3. Internal services and business logic
import { getUserByIdService } from '@/services/facades/user-service-facade'
import { getUserByIdRepository } from '@/db/repositories/user-repository'

// 4. UI components and utilities
import { Button, Card } from '@/components/ui'
import { UserProfile } from '@/components/features/user'
import { formatDate } from '@/lib/helper/date-helper'

// 5. Types and constants
import type { User } from '@/services/types/domain'
import { MAX_RETRY_ATTEMPTS } from '@/lib/constants'

// 6. Relative imports (local to current file)
import { localHelper } from './local-helper'
import './styles.css'

export function MyComponent() {
  // Component implementation
}
Path Aliases Configuration:
// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"],                        // Root src alias
      "@/components/*": ["./src/components/*"],  // Components
      "@/services/*": ["./src/services/*"],      // Services
      "@/db/*": ["./src/db/*"],                  // Database
      "@/lib/*": ["./src/lib/*"],                // Utilities
      "@/app/*": ["./src/app/*"],                // App routes
      "@/i18n/*": ["./src/i18n/*"]               // i18n
    }
  }
}

Code Organization Patterns

Consistent patterns for organizing code within files and directories:
Service File Organization (ShipSaaS Pattern):
// src/services/user-service.ts

// 1. Imports (organized by groups)
import { eq } from 'drizzle-orm'
import type { User, CreateUserInput, UserUpdate } from './types/domain'
import { createUserValidationSchema } from './validation/user-validation'
import { canEditUser, canDeleteUser } from './authorization/user-authorization'
import { getUserByIdRepository, createUserRepository } from '@/db/repositories/user-repository'

// 2. Main service functions (public)
export const getUserByIdService = async (id: string): Promise<User | null> => {
  const user = await getUserByIdRepository(id)
  return user ?? null
}

export const createUserService = async (data: CreateUserInput): Promise<User> => {
  const validated = createUserValidationSchema.parse(data)
  return await createUserRepository(validated)
}

export const updateUserService = async (
  id: string,
  data: UserUpdate,
  userId: string
): Promise<User> => {
  // Authorization check
  if (!canEditUser(userId, id)) {
    throw new AuthorizationError('Cannot edit this user')
  }

  // Validation and update
  return await updateUserRepository(id, data)
}

export const deleteUserService = async (id: string, userId: string): Promise<void> => {
  if (!canDeleteUser(userId, id)) {
    throw new AuthorizationError('Cannot delete this user')
  }
  await deleteUserRepository(id)
}
Service Facade Organization:
// src/services/facades/user-service-facade.ts
// Public API - only exports from this file should be imported externally

export {
  getUserByIdService,
  createUserService,
  updateUserService,
  deleteUserService,
  listUsersService
} from '../user-service'

export type { User, CreateUserInput, UserUpdate } from '../types/domain'
Component File Organization (ShipSaaS Pattern):
// src/components/features/user/UserProfile.tsx

'use client'

// 1. Imports (organized)
import { useState } from 'react'
import { Button, Card } from '@/components/ui'
import { useUserData } from './hooks'
import type { User } from '@/services/types/domain'

// 2. Types and interfaces (local to component)
interface UserProfileProps {
  userId: string
  onEdit?: () => void
}

// 3. Component implementation
export default function UserProfile({ userId, onEdit }: UserProfileProps) {
  const [isLoading, setIsLoading] = useState(false)
  const { user, error } = useUserData(userId)

  if (isLoading) return <div>Loading...</div>
  if (error) return <div>Error loading user</div>
  if (!user) return <div>User not found</div>

  return (
    <Card>
      <h1>{user.name}</h1>
      <p>Email: {user.email}</p>
      {onEdit && <Button onClick={onEdit}>Edit Profile</Button>}
    </Card>
  )
}
Repository File Organization:
// src/db/repositories/user-repository.ts

import { eq } from 'drizzle-orm'
import db from '../client'
import { usersTable } from '../models/user'
import type { User, NewUser } from '../models/user'

// Query functions
export const getUserByIdRepository = async (id: string): Promise<User | null> => {
  const result = await db.query.usersTable.findFirst({
    where: eq(usersTable.id, id)
  })
  return result ?? null
}

export const createUserRepository = async (data: NewUser): Promise<User> => {
  const [user] = await db.insert(usersTable).values(data).returning()
  return user
}

export const updateUserRepository = async (
  id: string,
  data: Partial<User>
): Promise<User> => {
  const [user] = await db
    .update(usersTable)
    .set(data)
    .where(eq(usersTable.id, id))
    .returning()
  return user
}

export const deleteUserRepository = async (id: string): Promise<void> => {
  await db.delete(usersTable).where(eq(usersTable.id, id))
}

Best Practices

📁

File Organization

Feature-based organization in components
Barrel exports (index.ts) for clean paths
Consistent kebab-case for files/directories
Single responsibility per file
Co-locate related code (components + hooks + types)
Tests alongside source code (tests/)
⚙️

Service Layer Pattern

Validate input with Zod schemas
Check authorization before operations
Use repositories for data access
Expose via facades (public API only)
Handle errors with custom classes
Return typed results
📥

Import Management

Grouped imports by type (libs, services, components)
Use path aliases (@/, @/services, etc.)
Import from facades (never direct services)
Type-only imports for types (import type )
Relative imports for local components
Prevent cycles with layered imports
🏷️

Naming Consistency

PascalCase: Components & Context (UserProfile.tsx)
kebab-case: Services & utilities (user-service.ts)
camelCase: Hooks & functions (useUserData, getData)
SCREAMING_SNAKE_CASE: Constants only
Suffixes: -service, -facade, -validation, -helper
Prefixes: use (hooks), can/has/is (booleans)
🗄️

Database Layer

Define schemas in models/ (Drizzle)
Create repositories for data access
Use in services (never in components)
Export types from models for services
Handle migrations via drizzle-kit
Type-safe queries with Drizzle ORM
🧪

Testing Strategy

Unit tests for services and utilities
Component tests with Testing Library
Integration tests for service workflows
E2E tests with Playwright
Mock external services (Stripe, email)
Test both authorized & unauthorized access
🔒

Security & Authorization

Check auth at service layer entry
Validate permissions before operations
Use authorization modules (RBAC)
Protect API routes with middleware
Validate input with Zod schemas
Handle errors gracefully and securely
📦

Monolithic Structure

Single Next.js app (not monorepo)
Clear layer separation: Routes → Components → Services → DB
Feature-based grouping in components
Domain-based organization in services
Shared lib/ for utilities
Scalable for growth with clear patterns
ShipSaaS structure mastered! Your codebase follows clean, layered patterns that make development fast and maintainable. The monolithic structure with clear separation of concerns allows you to scale features efficiently while keeping the code organized and type-safe.
    Project Structure | ShipSaaS Documentation | ShipSaaS - Launch your SaaS with AI in days