# Convex Turbo Monorepo

A production-ready Turborepo starter with Next.js, Expo, self-hosted Convex, and
embedded Payload CMS for live-editable marketing content. Built with TypeScript,
Tailwind CSS, and modern tooling.

---

## What's Inside?

### Apps & Packages

- **`apps/next`** - Next.js 16 web application with App Router
- **`apps/expo`** - Expo 54 mobile application _(in progress)_
- **`@gib/backend`** - Self-hosted Convex backend with authentication
- **`@gib/ui`** - Shared shadcn/ui component library
- **`@gib/eslint-config`** - ESLint configuration
- **`@gib/prettier-config`** - Prettier configuration with import sorting
- **`@gib/tailwind-config`** - Tailwind CSS v4 configuration
- **`@gib/tsconfig`** - Shared TypeScript configurations

### Tech Stack

- **Framework:** Next.js 16 (App Router) + Expo 54
- **Backend:** Convex (self-hosted)
- **Auth:** @convex-dev/auth with Authentik OAuth & Password providers
- **CMS:** Payload CMS embedded inside the Next.js app
- **Styling:** Tailwind CSS v4 + shadcn/ui
- **Language:** TypeScript (strict mode)
- **Package Manager:** Bun
- **Monorepo:** Turborepo
- **Deployment:** Docker (standalone)

---

## Getting Started

This is a self-hosted template. The full setup requires a server (home server or VPS)
to host the Convex backend and dashboard, and a reverse proxy (nginx-proxy-manager is
recommended) to expose them over HTTPS. The Next.js app can run locally in dev mode
once the Convex containers are reachable.

### Prerequisites

- [Bun](https://bun.sh) (v1.2+)
- [Docker](https://www.docker.com/) & Docker Compose (for self-hosted Convex)
- Node.js 22+ (for compatibility)
- A running nginx-proxy-manager instance (or similar reverse proxy) to expose Convex over HTTPS

---

### Step 1 — Clone & Install

```bash
git clone https://git.gbrown.org/gib/convex-monorepo
cd convex-monorepo
bun install
```

If you're using this as a template for a new project, remove the existing remote and
add your own:

```bash
git remote remove origin
git remote add origin https://your-git-host.com/your/new-repo.git
```

---

### Step 2 — Configure the Single Root Environment File

```bash
cp .env.example .env
```

The root `/.env` is the single required env file for this repo. It is used for:

- app/package runtime env vars
- Next.js build-time env vars
- Payload env vars
- Docker Compose interpolation
- helper scripts in `scripts/`

Fill it in with your values. The Docker-related variables in the same file control the
Convex containers and Next.js container — you'll need to choose:

- `INSTANCE_NAME` — a unique name for your Convex instance
- `INSTANCE_SECRET` — a secret string (generate something random)
- `CONVEX_CLOUD_ORIGIN` — the public HTTPS URL for your Convex backend API (e.g. `https://api.convex.example.com`)
- `CONVEX_SITE_ORIGIN` — the public HTTPS URL for Convex Auth HTTP routes (e.g. `https://api.convex.example.com`)
- `NEXT_PUBLIC_DEPLOYMENT_URL` — the URL for the Convex dashboard (e.g. `https://dashboard.convex.example.com`)
- `NEXT_CONTAINER_NAME`, `BACKEND_CONTAINER_NAME`, and `DASHBOARD_CONTAINER_NAME` — the Docker service container names

Do not create or rely on a separate `docker/.env`.

---

### Step 3 — Start the Convex Containers

```bash
./scripts/docker-compose up -d backend dashboard
```

Wait a moment for `convex-backend` to pass its health check, then verify both
containers are running:

```bash
./scripts/docker-compose ps
```

Reverse-proxy the two Convex services through nginx-proxy-manager (or your preferred
proxy) to the URLs you chose in Step 2. Both must be reachable over HTTPS before you
can proceed.

---

### Step 4 — Generate the Convex Admin Key

With the backend container running, generate the admin key:

```bash
./scripts/generate-convex-admin-key
```

Copy the printed key — you'll need it as `CONVEX_SELF_HOSTED_ADMIN_KEY` in the root
`.env` file.

---

### Step 5 — Finish Configuring Root Environment Variables

Fill out all values in root `/.env`:

```bash
# Next.js
NODE_ENV=development
SENTRY_AUTH_TOKEN=               # From your self-hosted Sentry
PAYLOAD_SECRET=                  # openssl rand -hex 32
PAYLOAD_DB_URL=postgresql://user:password@host:5432/db_name
NEXT_PUBLIC_SITE_URL=https://example.com
NEXT_PUBLIC_CONVEX_URL=https://api.convex.example.com
NEXT_PUBLIC_PLAUSIBLE_URL=https://plausible.example.com
NEXT_PUBLIC_SENTRY_DSN=
NEXT_PUBLIC_SENTRY_URL=https://sentry.example.com
NEXT_PUBLIC_SENTRY_ORG=sentry
NEXT_PUBLIC_SENTRY_PROJECT_NAME=my-project

# Convex
CONVEX_SELF_HOSTED_URL=https://api.convex.example.com
CONVEX_SELF_HOSTED_ADMIN_KEY=    # From Step 4
CONVEX_SITE_URL=http://localhost:3000  # Always localhost:3000 for local dev

# Auth (synced to Convex in Step 6)
USESEND_API_KEY=
USESEND_URL=https://usesend.example.com
USESEND_FROM_EMAIL=My App <noreply@example.com>
AUTH_AUTHENTIK_ID=
AUTH_AUTHENTIK_SECRET=
AUTH_AUTHENTIK_ISSUER=https://auth.example.com/application/o/my-app/

# Docker Compose
NETWORK=nginx-bridge
NEXT_CONTAINER_NAME=next-app
BACKEND_CONTAINER_NAME=convex-backend
DASHBOARD_CONTAINER_NAME=convex-dashboard
INSTANCE_NAME=convex
INSTANCE_SECRET=
CONVEX_CLOUD_ORIGIN=https://api.convex.example.com
CONVEX_SITE_ORIGIN=https://convex.convex.example.com
NEXT_PUBLIC_DEPLOYMENT_URL=https://dashboard.convex.example.com
POSTGRES_URL=postgresql://user:password@host:5432/convex
```

---

### Step 6 — Generate JWT Keys & Sync Environment Variables to Convex

Generate the RS256 JWT keypair needed for Convex Auth:

```bash
cd packages/backend
bun run scripts/generateKeys.mjs
```

This prints `JWT_PRIVATE_KEY` and `JWKS` values. Sync them to your Convex deployment
along with all other backend environment variables:

```bash
# From packages/backend/
bun with-env npx convex env set JWT_PRIVATE_KEY "your-private-key"
bun with-env npx convex env set JWKS "your-jwks"
bun with-env npx convex env set AUTH_AUTHENTIK_ID "your-client-id"
bun with-env npx convex env set AUTH_AUTHENTIK_SECRET "your-client-secret"
bun with-env npx convex env set AUTH_AUTHENTIK_ISSUER "your-issuer-url"
bun with-env npx convex env set USESEND_API_KEY "your-api-key"
bun with-env npx convex env set USESEND_URL "https://usesend.example.com"
bun with-env npx convex env set USESEND_FROM_EMAIL "My App <noreply@example.com>"
```

**For production auth to work**, you must also update `CONVEX_SITE_URL` in the Convex
Dashboard to your production Next.js URL. Go to
`https://dashboard.convex.example.com` → Settings → Environment Variables and set:

```
CONVEX_SITE_URL = https://example.com
```

The root `.env` value of `http://localhost:3000` is correct for local dev and should
not be changed — only update it in the Dashboard for production.

---

### Step 7 — Start the Development Server

```bash
# From project root
bun dev:next          # Next.js app + Convex backend (most common)
# or
bun dev               # All apps (Next.js + Expo + Backend)
```

**App URLs:**

- **Next.js:** http://localhost:3000
- **Convex Dashboard:** https://dashboard.convex.example.com

### Docker Helper Scripts

This repo includes helper scripts so you do not have to keep passing `--env-file` and
`-f` manually to Docker Compose.

Useful commands:

```bash
./scripts/docker-compose ps
./scripts/docker-compose up -d backend dashboard
./scripts/docker-compose up -d next
./scripts/build-next-app
./scripts/update-next-app
./scripts/update-convex
./scripts/generate-convex-admin-key
```

`./scripts/docker-compose` also accepts short service aliases like `next`, `backend`,
and `dashboard` and maps them to the container names from root `/.env`.

---

## Development Commands

```bash
# Development
bun dev                      # Run all apps
bun dev:next                 # Next.js + Convex backend
bun dev:expo                 # Expo + Convex backend
bun dev:backend              # Convex backend only

# Quality Checks
bun typecheck                # Type checking (recommended for testing)
bun lint                     # Lint all packages
bun lint:fix                 # Lint and auto-fix
bun format                   # Check formatting
bun format:fix               # Fix formatting

# Build
bun build                    # Build all packages (production)

# Utilities
bun ui-add                   # Add shadcn/ui components
bun clean                    # Clean node_modules
bun clean:ws                 # Clean workspace caches
```

### Single Package Commands

Use Turborepo filters to target specific packages:

```bash
bun turbo run dev -F @gib/next           # Run Next.js only
bun turbo run typecheck -F @gib/backend  # Typecheck backend
bun turbo run lint -F @gib/ui            # Lint UI package
```

---

## Project Structure

```
convex-monorepo/
├── apps/
│   ├── next/                # Next.js web app
│   │   ├── src/
│   │   │   ├── app/         # App Router pages
│   │   │   ├── components/  # React components
│   │   │   └── lib/         # Utilities
│   │   └── package.json
│   └── expo/                # Expo mobile app (WIP)
│
├── packages/
│   ├── backend/             # Convex backend
│   │   ├── convex/          # Convex functions (synced to deployment)
│   │   ├── scripts/         # Utilities (generateKeys.mjs)
│   │   └── types/           # Shared types
│   └── ui/                  # shadcn/ui components
│       └── src/             # Component source files
│
├── tools/                   # Shared configurations
│   ├── eslint/
│   ├── prettier/
│   ├── tailwind/
│   └── typescript/
│
├── docker/                  # Self-hosted deployment
│   ├── compose.yml
│   ├── Dockerfile
│   └── data/                # Convex data volume (gitignored in real use)
│
├── scripts/                 # Docker/deployment helper scripts using root .env
│   ├── docker-compose
│   ├── build-next-app
│   ├── update-next-app
│   └── update-convex
│
├── turbo.json              # Turborepo configuration
└── package.json            # Root workspace & catalogs
```

---

## Features

### Authentication

- **OAuth:** Authentik SSO integration
- **Password:** Custom password auth with email verification
- **OTP:** Email verification via self-hosted UseSend
- **Session Management:** Secure cookie-based sessions (30-day max age)

### Next.js App

- **App Router:** Next.js 16 with React Server Components
- **Editable Marketing Content:** Payload-backed landing page with live preview
- **Data Preloading:** SSR data fetching with `preloadQuery` + `usePreloadedQuery`
- **Middleware:** Route protection & IP-based security (`src/proxy.ts`)
- **Styling:** Tailwind CSS v4 with dark mode (OKLCH-based theme)
- **Analytics:** Plausible (privacy-focused, proxied through Next.js)
- **Monitoring:** Sentry error tracking & performance

### Backend

- **Real-time:** Convex reactive queries
- **File Storage:** Built-in file upload/storage
- **Auth:** Multi-provider authentication
- **Type-safe:** Full TypeScript with generated types
- **Self-hosted:** Complete control over your data

### Developer Experience

- **Monorepo:** Turborepo for efficient builds and caching
- **Type Safety:** Strict TypeScript throughout
- **Code Quality:** ESLint + Prettier with auto-fix
- **Hot Reload:** Fast refresh for all packages
- **Catalog Deps:** Centralized dependency version management

---

## Deployment

### Production Deployment (Docker)

Once the Convex containers are running (they only need to be started once), deploying
a new version of the Next.js app is a one-command workflow:

```bash
./scripts/build-next-app
```

To start all services from scratch:

```bash
./scripts/docker-compose up -d backend dashboard
# Wait for backend health check to pass, then:
./scripts/docker-compose up -d next
```

**Services:**

- `next-app` — Next.js standalone build
- `convex-backend` — Convex backend (port 3210)
- `convex-dashboard` — Admin dashboard (port 6791)

**Network:** Uses `nginx-bridge` Docker network (reverse proxy via nginx-proxy-manager).

### Production Checklist

- [ ] Fill out root `/.env` with all app, Payload, and Docker Compose values
- [ ] Start `convex-backend` and `convex-dashboard` containers
- [ ] Generate and set `CONVEX_SELF_HOSTED_ADMIN_KEY` via `./scripts/generate-convex-admin-key`
- [ ] Reverse-proxy both Convex services via nginx-proxy-manager with SSL
- [ ] Generate JWT keys and sync all env vars to Convex (`bun with-env npx convex env set ...`)
- [ ] Update `CONVEX_SITE_URL` in the Convex Dashboard to your production Next.js URL
- [ ] Build and start the `next-app` container
- [ ] Back up `docker/data/` regularly (contains all Convex database data)

---

## Documentation

- **[AGENTS.md](./AGENTS.md)** — Comprehensive guide for AI agents & developers
- **[docs/payload-cms.md](./docs/payload-cms.md)** — How Payload is wired up and how to extend it in this template
- **[Convex Docs](https://docs.convex.dev)** — Official Convex documentation
- **[Turborepo Docs](https://turbo.build/repo/docs)** — Turborepo documentation
- **[Next.js Docs](https://nextjs.org/docs)** — Next.js documentation

---

## Troubleshooting

### Backend typecheck shows TypeScript help message

This is expected behavior. The backend package follows Convex's structure with only
`convex/tsconfig.json` (no root tsconfig). Running `bun typecheck` from the repo root
will show TypeScript's help text for `@gib/backend` — this is not an error.

### Imports from Convex require `.js` extension

The project uses ESM (`"type": "module"`), which requires explicit file extensions:

```typescript
// ✅ Correct
import type { Id } from '@gib/backend/convex/_generated/dataModel.js';
// ❌ Wrong — will fail at runtime
import { api } from '@gib/backend/convex/_generated/api';
import { api } from '@gib/backend/convex/_generated/api.js';
```

### Docker containers won't start

1. Check Docker logs: `./scripts/docker-compose logs`
2. Verify environment variables in root `/.env`
3. Ensure the `nginx-bridge` network exists: `sudo docker network create nginx-bridge`
4. Check that the required ports (3210, 6791) are not already in use

### Only one `.env` file matters

Use the root `/.env` for everything in this repo, including Docker helper scripts and
Docker Compose interpolation. Do not create or rely on a separate `docker/.env`.

### Auth doesn't work in production

Make sure `CONVEX_SITE_URL` is set to your production Next.js URL in the **Convex
Dashboard** (not just in the root `.env` file). The root `.env` should always contain
`http://localhost:3000`; the Dashboard must have your production URL.

### Catalog updates break workspace

After updating dependencies, if you see `sherif` errors on `bun install`:

1. Never use `bun update` inside individual package directories
2. Edit the version in root `package.json` catalog section instead
3. Run `bun install` from the root
4. Verify with `bun lint:ws`

---

## Contributing

This is a personal monorepo template. Feel free to fork and adapt for your needs.

### Code Style

- Single quotes, trailing commas
- 80 character line width
- ESLint + Prettier enforced
- `const fn = () => {}` over `function fn()` (strong preference)
- Import order: Types → React → Next → Third-party → @gib → Local

Run `bun lint:fix` and `bun format:fix` before committing.

---

## License

MIT

---

## Acknowledgments

Built with inspiration from:

- [T3 Turbo](https://github.com/t3-oss/create-t3-turbo)
- [Convex](https://convex.dev)
- [shadcn/ui](https://ui.shadcn.com)
- [Turborepo](https://turbo.build)
