Skip to main content

Monorepo Architecture

Soku is organized as a pnpm workspace monorepo with Turborepo for task orchestration. The repository contains four application packages and two shared library packages, all managed from a single root.

Directory Structure

sokuho/
├── apps/
│ ├── web/ # Next.js 16 web application (App Router)
│ ├── functions/ # Firebase Cloud Functions (Node.js 22)
│ ├── docs/ # Docusaurus 3.5 documentation site
│ └── cypress/ # Cypress 15 E2E test suite
├── packages/
│ ├── schema/ # @soku/schema — shared Zod schemas
│ └── shared/ # @repo/shared — shared utilities
├── scripts/
│ └── sync-schema.js # Copies built schema into functions
├── cors.json # Firebase Storage CORS configuration
├── cypress.config.js # Cypress configuration
├── firebase.json # Firebase project configuration
├── firestore.indexes.json # Firestore composite indexes
├── firestore.rules # Firestore security rules
├── package.json # Root workspace scripts and devDependencies
├── pnpm-lock.yaml # Lockfile
├── pnpm-workspace.yaml # Workspace package declarations
├── storage.rules # Firebase Storage security rules
├── tsconfig.base.json # Shared TypeScript configuration
└── turbo.json # Turborepo pipeline configuration

Workspace Packages

apps/web — Next.js Web Application

The primary user-facing application. Built with Next.js 16.0.8, React 19.2.1, and the App Router. Contains the full dashboard, post creation flow, integrations management, billing, templates, automations, and a public landing page. Published as a private package named web.

Key dependencies include Redux Toolkit 2.9.0 for client state, TanStack React Query v5 with localStorage persistence for server state, Tailwind CSS ~3.4 for styling, Storybook 10 for component development, and Stripe for billing. See Web App Architecture for full details.

apps/functions — Firebase Cloud Functions

The backend layer, published as @repo/functions. Runs on Node.js 22 with firebase-functions v6 and Express 5 for the public API. Handles OAuth flows for eight social platforms, content publishing and orchestration via Cloud Tasks, webhook processing (Stripe, Meta), media processing (Sharp, FFmpeg), AI features (OpenAI SDK 4.56.0), and scheduled maintenance jobs. See Functions Architecture for full details.

apps/docs — Docusaurus Documentation

Internal documentation site built with Docusaurus 3.5.2. Contains architecture docs, provider integration guides, setup instructions, and developer references. Runs on its own dev server at port 3100.

apps/cypress — E2E Test Suite

End-to-end tests using Cypress 15. Contains test specs in e2e/, reusable fixtures in fixtures/, and custom commands in support/. Cypress configuration lives at the repository root in cypress.config.js.

packages/schema@soku/schema

Shared Zod schema definitions used by both the web app and functions. Published as @soku/schema (version 0.0.2). Contains 30+ schema modules organized into two categories:

  • Domain schemas (src/domain/): Core business types including platform, integration, posts, templates, subscription, credits, media, library, automations, jobs, queues, tiers, user, captions, youtube, dashboard, tracing, logs, config, api-keys, api-requests, and primitives.
  • API schemas (src/api/): Request/response shapes for the public API including posts, media, api-keys, ai, templates, billing, and shared utilities.

Built with tsup to produce both ESM and CJS output plus TypeScript declarations. The web app consumes it via workspace:*, while functions receive a file copy via the sync:schema script (since Firebase deploy bundles from the functions directory).

packages/shared@repo/shared

Lightweight shared utilities package consumed by the web app via workspace:^. Currently minimal, serving as the extension point for any cross-app utility functions.

Build and Dependency Graph

Turborepo manages the task pipeline. The dependency graph flows upward from packages to apps:

@soku/schema ──────┬──> web

└──> @repo/functions (via file copy)

@repo/shared ──────────> web

Task Pipeline (turbo.json)

TaskDependenciesOutputsCache
build^build (builds dependencies first).next/**, dist/**Yes
devNoneNoneNo (persistent)
lintNoneNoneYes
typecheckNoneNoneYes
test^buildcoverage/**Yes

The ^build dependency ensures that @soku/schema is built before any app that depends on it. The dev task runs in parallel across workspaces with the --parallel flag.

Global Environment Variables

Turborepo hashes these environment variables for cache invalidation:

  • NEXT_PUBLIC_FIREBASE_API_KEY
  • NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN
  • NEXT_PUBLIC_FIREBASE_PROJECT_ID
  • NEXT_PUBLIC_FIREBASE_APP_ID

Root Configuration Files

pnpm-workspace.yaml

Declares the two workspace globs:

packages:
- "apps/*"
- "packages/*"

package.json (Root)

Specifies pnpm@9.12.2 as the package manager and node: "22" as the engine. Contains workspace-level scripts and shared devDependencies:

ScriptDescription
devRuns all apps in parallel via Turborepo
dev:webRuns only the web app
dev:functionsRuns only functions (Firebase emulators)
buildBuilds all packages and apps
testRuns tests across all packages (excludes docs)
lintLints all packages
typecheckType-checks all packages
checkRuns lint + typecheck + test (excludes docs)
ciFull CI pipeline: lint, typecheck, test, build
deploy:functionsDeploys functions to Firebase
deploy-functionDeploys a single function by name
sync:schemaBuilds schema and copies to functions
prepareSets up Husky git hooks

Shared devDependencies at root: @types/node (22+), cypress (15), eslint (9), husky (9), prettier (2.8.8), tsup (8), and turbo (2.5).

tsconfig.base.json

Shared TypeScript configuration inherited by all packages:

  • Target: ES2022
  • Module: ESNext with Bundler resolution
  • Strict mode enabled
  • JSX: react-jsx
  • Path alias: @soku/schema maps to packages/schema/src

firebase.json

Configures the Firebase project:

  • Functions: Source at apps/functions, runtime nodejs22, predeploy runs sync:schema
  • Hosting: Two sites (sokuho-vai and sokuho-storage) plus their staging equivalents, routing /v1/** to the api function and /** to the media function
  • Firestore: Rules and indexes from root files
  • Storage: Rules from storage.rules
  • Emulators: Functions (5001), Firestore (8080), Storage (9199) with UI enabled

firestore.rules and storage.rules

Security rules for Firestore and Cloud Storage, defined at the repository root and deployed via firebase deploy.

Key Architectural Decisions

Why pnpm + Turborepo

pnpm provides strict dependency isolation (no phantom dependencies) and efficient disk usage via content-addressable storage. Turborepo adds incremental builds with remote caching support, parallel execution, and dependency-aware task ordering.

Why a Shared Schema Package

Zod schemas in @soku/schema provide a single source of truth for data shapes. Both the web app and functions validate against the same schemas, eliminating type drift between frontend and backend. The sync:schema script ensures functions always deploy with the latest schema build.

Why File Copy for Functions

Firebase Cloud Functions deploy from a self-contained directory. Unlike the web app, which can resolve workspace dependencies at build time, functions need all dependencies present in their directory at deploy time. The sync:schema script builds the schema package and copies the output into apps/functions/.schema/, which functions reference as "@soku/schema": "file:.schema" in their package.json.

Node.js 22 Everywhere

Both the root package.json and apps/functions/package.json specify Node.js 22. The Firebase runtime is configured as nodejs22 in firebase.json. This ensures consistent behavior between local development and production.

ESM Throughout

Both @soku/schema and @repo/functions use "type": "module" for native ESM. The web app uses ESM via Next.js bundling. This avoids mixed module system issues across the monorepo.

How to Add a New Package

  1. Create the package directory under packages/ (for shared libraries) or apps/ (for applications):
mkdir -p packages/my-lib/src
  1. Add a package.json with a name matching the @soku/ or @repo/ convention:
{
"name": "@soku/my-lib",
"version": "0.0.1",
"private": true,
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsup src/index.ts --format esm,cjs --out-dir dist",
"dev": "tsup src/index.ts --format esm,cjs --out-dir dist --watch",
"lint": "echo 'lint placeholder'",
"typecheck": "tsc --noEmit"
}
}
  1. Add a tsconfig.json that extends the base config:
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"]
}
  1. Install dependencies and reference from consumers:
pnpm install
# From an app that needs it:
pnpm --filter web add @soku/my-lib@workspace:*
  1. If functions need the new package, add a sync step similar to sync-schema.js and update the firebase.json predeploy command.

  2. Run pnpm build from the root to verify the dependency graph resolves correctly.