Project Structure
ShipMore is organized into feature domains — content/CMS, monetization, search & explore, import, and monitoring, plus auth — wired together by a thin root layer. Each domain owns its own data and logic for one concern; nothing reaches across a domain boundary to mutate another’s state.
How it fits together
- Domains own the work. A domain exposes pure services and utilities for its concern. It doesn’t hold server actions, and it doesn’t reach for root-owned clients on its own.
- A single service layer is where business rules live. Every interface — admin UI, REST endpoint, MCP tool, CLI command, webhook — is a thin adapter on top of that layer. The same logic backs all of them, so a rule is written once and behaves identically everywhere.
- The root layer wires domains together. It owns the shared clients and the composition glue, and passes dependencies down into the domains.
Root orientation
A few root-level concerns sit above the domains:
| Concern | What lives there |
|---|---|
| Server actions | All 'use server' actions — the only place they’re defined. They call domain services and inject dependencies. |
| Shared clients | Root-owned clients (Payload, Stripe, email, auth) instantiated once and passed in. |
| App routes | Public tenant-facing routes and the Payload admin panel + API. |
| CLI | The shipmore CLI source, built to a standalone binary. |
| Docs | This documentation (MDX), served by Nextra. |
Principles
A few rules keep the structure honest as the system grows: business logic lives in the service layer (never in an individual handler), so a rule is written once and behaves the same across every interface; and domains stay decoupled from the shared clients, which are wired in at the root rather than reached for directly.