Monorepo and Tech Stack
Purpose and Scope
Dub is organized as a private AGPL-licensed pnpm workspace named dub-monorepo. The repository is not just a single Next.js application: it combines the hosted web product, shared internal packages, publishable packages, integrations, and build orchestration under one workspace. For contributors, the important mental model is that root commands describe repository-wide workflows, while package-level scripts describe what each workspace member actually does when Turborepo invokes a task. This page explains that structure so you can choose the right command, understand dependency boundaries, and reason about why a change in one package may affect builds elsewhere.
Sources: README.md, package.json, pnpm-workspace.yaml, turbo.json, apps/web/package.json
The product context matters because the technical stack follows Dub's public surface area. The README describes Dub as an open-source link attribution platform for short links, conversion tracking, and affiliate programs, and the dependency graph reflects that broad scope. The web app brings together authentication, analytics, database access, payments, email, embeddings, UI components, API generation, and testing. The workspace packages let the project share those concerns without duplicating code across the application, CLI, embeds, emails, utilities, and design-system layers.
Sources: README.md, apps/web/package.json
Relevant Source Files
README.md- Introduces Dub, lists the top-level technology stack, documents recommended Node and pnpm versions, and points contributors toward local development and self-hosting workflows.package.json- Defines the private monorepo package, AGPL license, root scripts, package publishing commands, root dev dependencies, and the required pnpm package manager version.pnpm-workspace.yaml- Declares which directories participate in the pnpm workspace:apps/*,apps/web/.react-email,packages/*, andpackages/embeds/*.turbo.json- Configures Turborepo task behavior for build, dev, clean, and test, including dependency ordering, caching, persistent development tasks, and build outputs.apps/web/package.json- Defines the main web application scripts and its major runtime dependencies, including Next.js, Prisma, NextAuth, Tinybird-related analytics tooling, Upstash, Stripe, Resend-adjacent packages, workspace packages, Playwright, and Vitest.
Workspace Layout and Package Boundaries
The workspace is declared explicitly instead of relying on implicit folder discovery. pnpm-workspace.yaml includes top-level applications under apps/*, all packages under packages/*, nested embed packages under packages/embeds/*, and the generated React Email workspace at apps/web/.react-email. That layout means package managers and Turborepo can resolve local workspace dependencies consistently, including references such as @dub/ui, @dub/utils, @dub/email, @dub/embed-react, @dub/tailwind-config, and the shared tsconfig package. When a package depends on another workspace package with workspace:*, pnpm links the local source instead of fetching a published version.
Sources: pnpm-workspace.yaml, package.json, apps/web/package.json
The root package.json keeps repository-wide concerns at the top level. It marks the monorepo as private, declares the license as AGPL-3.0-or-later, pins the package manager to pnpm@9.15.9, and defines commands that delegate to Turborepo. The README reinforces the expected local toolchain by recommending Node v23.11.0 and pnpm 9.15.9. In practice, those version signals are part of the stack: mismatched Node or pnpm versions can make lockfile resolution, Next.js builds, Prisma generation, or Turborepo caching behave differently from CI and maintainer environments.
Sources: README.md, package.json
Turborepo Workflow
Turborepo is the repository's task runner, and the root scripts are intentionally thin wrappers around Turbo tasks. pnpm build runs turbo build, pnpm dev runs turbo dev, pnpm lint runs turbo lint, pnpm clean runs turbo clean, and pnpm test runs turbo run test. The build:packages script is narrower: it uses pnpm recursive filtering to build only workspaces under ./packages/**. Publishing scripts follow a similar pattern by building a specific package with a Turbo filter, changing into that package directory, and running npm publish.
Example root commands:
pnpm dev
pnpm build
pnpm test
pnpm build:packages
Sources: package.json
The Turbo pipeline encodes the dependency rules behind those commands. A package's build depends on ^build, so dependencies build before dependents. Build outputs include .next/** and dist/**, while .next/cache/** is excluded, which keeps generated application and package artifacts cacheable without preserving the framework cache directory. The dev task disables caching and is marked persistent, matching the expectation that development servers keep running. The test task depends on upstream builds, which protects tests that need compiled dependency packages. The globalDependencies setting watches **/.env, so environment-file changes can invalidate relevant task state.
Sources: turbo.json
Main Web Application Stack
The web workspace is the primary application package. Its scripts show a Next.js-centered workflow with Prisma as a required preparation step: dev, build, and test all run pnpm prisma:generate before launching the relevant tool. Local development starts Next with Turbopack on port 8888 through concurrently, production builds use next build, and runtime startup uses next start. The app also owns operational scripts such as generate-openapi, prisma:push, prisma:studio, and prisma:format, making it the place where API schema generation and database schema workflows are coordinated.
Sources: apps/web/package.json
The dependency list in apps/web/package.json is a compact map of Dub's runtime architecture. Next.js 15.5.8, React, TypeScript-oriented tooling, and Tailwind-oriented workspace packages support the application layer. Prisma Client, the PlanetScale adapter, and @planetscale/database represent database access. NextAuth, the Prisma adapter, BoxyHQ SAML Jackson, and related identity packages support authentication and SSO. Upstash packages cover Redis, rate limiting, vector search, QStash, and workflows. Analytics-related dependencies include @dub/analytics, @chronark/zod-bird, and visualization libraries such as Visx. Stripe, Slack, Intercom, OpenAPI types, AI SDK packages, and workspace-local Dub packages fill out product integrations and shared UI/business logic.
Sources: apps/web/package.json, README.md
System-to-Code Mapping
| Concern | Source-backed implementation signal |
|---|---|
| Product and stack overview | README.md names Dub's product scope and lists Next.js, TypeScript, Tailwind, Prisma, Upstash, Tinybird, PlanetScale, NextAuth.js, BoxyHQ, Turborepo, Stripe, Resend, and Vercel. |
| Workspace membership | pnpm-workspace.yaml includes apps/*, apps/web/.react-email, packages/*, and packages/embeds/*. |
| Repository orchestration | package.json exposes root scripts that delegate build, dev, lint, clean, and test to Turborepo. |
| Task dependencies and caching | turbo.json defines build, dev, clean, and test pipeline behavior. |
| Main application runtime | apps/web/package.json defines Next.js, Prisma, tests, OpenAPI generation, and the application's dependency set. |
This mapping is useful when deciding where a change belongs. A new product route, API route, dashboard workflow, or database-backed behavior normally starts in apps/web. A reusable UI primitive or utility should move into a workspace package only when it needs to be shared across boundaries. A build or release change usually belongs in root package.json or turbo.json, depending on whether it is a command users run or a task dependency/caching rule. A package inclusion problem usually starts with pnpm-workspace.yaml, because a folder outside the workspace globs will not be linked as a local workspace member.
Sources: package.json, pnpm-workspace.yaml, turbo.json, apps/web/package.json
Development and Build Implications
Because the web app generates Prisma artifacts before key commands, database schema tooling is part of normal development rather than a separate optional step. The README's troubleshooting guidance reflects this: if local database tables are missing, contributors are directed to run pnpm prisma:push; if local builds are inconsistent, they are told to verify Node and pnpm versions, remove generated directories such as node_modules, .next, and .turbo, reinstall with pnpm install, and rebuild. Those instructions align with the monorepo design: dependency installation, Turbo state, and framework outputs are shared sources of local build correctness.
Sources: README.md, apps/web/package.json
The repository also separates application development from package publishing. Day-to-day contributors generally use pnpm dev, package builds, tests, and app-level scripts. Maintainers publishing public packages use dedicated root scripts such as publish-cli, publish-embed-core, publish-embed-react, publish-tw, publish-ui, and publish-utils. Each publishing command builds the target package through Turbo first, then publishes from the package directory. That pattern keeps release workflows close to the root package manifest while still preserving package-local build definitions and package-local npm metadata.
Sources: package.json
Next Steps
If you are setting up a local environment, read the local development page next and follow the repository's Node and pnpm version guidance before running root scripts. If you are modifying product behavior, start with the web application package and then follow imports into workspace packages only when shared code is involved. If you are changing package exports, release behavior, or reusable primitives, use the package-specific pages for @dub/ui, @dub/utils, @dub/email, CLI, embeds, Stripe, and HubSpot integrations to understand their public contracts before editing root orchestration files.