Files
convex-monorepo/README.md

14 KiB

Convex Turbo Monorepo

A production-ready Turborepo starter with Next.js, Expo, and self-hosted Convex backend. 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
  • 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 (v1.2+)
  • Docker & 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

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:

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

Step 2 — Configure the Docker Environment

The docker/ directory contains everything needed to run the Convex backend and the Next.js app in production.

cd docker/
cp .env.example .env

Edit docker/.env and fill in your values. The variables below the Next.js app section control the Convex containers — 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)

Step 3 — Start the Convex Containers

cd docker/
sudo docker compose up -d convex-backend convex-dashboard

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

sudo 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:

cd docker/
./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 — Configure Root Environment Variables

Create the root .env file:

# From the repo root
cp .env.example .env

Fill out all values in /.env:

# Next.js
NODE_ENV=development
SENTRY_AUTH_TOKEN=               # From your self-hosted Sentry
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/

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

Generate the RS256 JWT keypair needed for Convex Auth:

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:

# 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

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

App URLs:


Development Commands

# 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:

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
│   └── .env.example
│
├── 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
  • 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 two-command workflow:

# SSH onto your server, then:
cd docker/

# Build the new image
sudo docker compose build next-app

# Deploy it
sudo docker compose up -d next-app

To start all services from scratch:

cd docker/
sudo docker compose up -d convex-backend convex-dashboard
# Wait for backend health check to pass, then:
sudo docker compose up -d next-app

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 docker/.env with your domain names and secrets
  • Start convex-backend and convex-dashboard containers
  • Generate and set CONVEX_SELF_HOSTED_ADMIN_KEY via ./generate_convex_admin_key
  • Reverse-proxy both Convex services via nginx-proxy-manager with SSL
  • Fill out root /.env with all environment variables
  • 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


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:

// ✅ 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: sudo docker compose logs
  2. Verify environment variables in docker/.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

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: