- Remove Convex from docker-compose (runs separately) - Add .forgejo/workflows/ci.yml with auto-deploy to Coolify - Update DEVELOPMENT.md with dev/prod architecture diagram - Rename branch from develop to dev in all workflows
437 lines
15 KiB
Markdown
437 lines
15 KiB
Markdown
# Development Guide
|
|
|
|
Personal cheat sheet for bootstrapping and deploying projects from this template.
|
|
|
|
## Local Development Setup
|
|
|
|
### 1. Clone & Install
|
|
|
|
```bash
|
|
git clone <your-forgejo-repo> my-project
|
|
cd my-project
|
|
pnpm install
|
|
```
|
|
|
|
### 2. Environment Variables
|
|
|
|
Copy `.env.example` to `.env.local` and fill in:
|
|
|
|
```bash
|
|
CONVEX_SELF_HOSTED_URL='https://convex-backend.mentat.ovh'
|
|
CONVEX_SELF_HOSTED_ADMIN_KEY='self-hosted-convex|...'
|
|
NEXT_PUBLIC_SITE_URL=http://localhost:3000
|
|
NEXT_PUBLIC_CONVEX_URL=https://convex-backend.mentat.ovh
|
|
NEXT_PUBLIC_CONVEX_SITE_URL=https://backend-site-xxx.mentat.ovh
|
|
```
|
|
|
|
> **Never commit `.env.local`** — it's in `.gitignore`.
|
|
|
|
### 3. Run Dev Server
|
|
|
|
```bash
|
|
pnpm dev --webpack
|
|
```
|
|
|
|
> ⚠️ **Never use Turbopack** — Next.js 16.2.1 has a CPU spike bug. Always `--webpack`.
|
|
|
|
### 4. Convex Setup (Local)
|
|
|
|
```bash
|
|
npx convex dev
|
|
```
|
|
|
|
This connects to your self-hosted Convex instance. Ensure CLI version matches your backend image version.
|
|
|
|
## Architecture Overview
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ LOCAL DEV │
|
|
│ pnpm dev --webpack → npx convex dev → DEV CONVEX DB │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ FORGEJO / GIT │
|
|
│ main branch ──► PROD DEPLOY │
|
|
│ dev branch ──► DEV DEPLOY │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
│
|
|
┌───────────────┴───────────────┐
|
|
▼ ▼
|
|
┌─────────────────────────────┐ ┌─────────────────────────────┐
|
|
│ COOLIFY (PROD) │ │ COOLIFY (DEV) │
|
|
│ Next.js Docker Container │ │ Next.js Docker Container │
|
|
│ (frontend) │ │ (frontend) │
|
|
└─────────────────────────────┘ └─────────────────────────────┘
|
|
│ │
|
|
▼ ▼
|
|
┌─────────────────────────────┐ ┌─────────────────────────────┐
|
|
│ CONVEX (PROD DB) │ │ CONVEX (DEV DB) │
|
|
│ Self-hosted on Coolify │ │ Self-hosted on Coolify │
|
|
│ or Convex Cloud │ │ or Convex Cloud │
|
|
└─────────────────────────────┘ └─────────────────────────────┘
|
|
```
|
|
|
|
### Convex Backend
|
|
|
|
Convex runs **separately** from the Next.js frontend. You need two instances:
|
|
- **Dev**: for local development + dev environment testing
|
|
- **Prod**: for production
|
|
|
|
On Coolify, create a new service for each Convex backend using the official docker-compose:
|
|
|
|
```yaml
|
|
services:
|
|
backend:
|
|
image: 'ghcr.io/get-convex/convex-backend:latest'
|
|
stop_grace_period: 10s
|
|
stop_signal: SIGINT
|
|
ports:
|
|
- '${PORT:-3210}:3210'
|
|
- '${SITE_PROXY_PORT:-3211}:3211'
|
|
volumes:
|
|
- 'data:/convex/data'
|
|
environment:
|
|
INSTANCE_NAME: '${INSTANCE_NAME}'
|
|
INSTANCE_SECRET: '${INSTANCE_SECRET}'
|
|
CONVEX_CLOUD_ORIGIN: '${SERVICE_URL_BACKEND}'
|
|
CONVEX_SITE_ORIGIN: '${SERVICE_URL_BACKEND_SITE}'
|
|
DO_NOT_REQUIRE_SSL: '${DO_NOT_REQUIRE_SSL:-true}'
|
|
DATABASE_URL: '${DATABASE_URL:-}'
|
|
healthcheck:
|
|
test: ['CMD-SHELL', 'curl -f http://localhost:3210/version']
|
|
interval: 5s
|
|
start_period: 10s
|
|
timeout: 5s
|
|
retries: 10
|
|
dashboard:
|
|
image: 'ghcr.io/get-convex/convex-dashboard:latest'
|
|
stop_grace_period: 10s
|
|
stop_signal: SIGINT
|
|
ports:
|
|
- '${DASHBOARD_PORT:-6791}:6791'
|
|
environment:
|
|
PORT: '6791'
|
|
NEXT_PUBLIC_DEPLOYMENT_URL: '${SERVICE_URL_BACKEND}'
|
|
depends_on:
|
|
backend:
|
|
condition: service_healthy
|
|
volumes:
|
|
data: null
|
|
```
|
|
|
|
> **Best practice**: Copy the [official self-hosted config](https://github.com/get-convex/convex-backend/blob/main/self-hosted/docker/docker-compose.yml) and adapt env vars for Coolify.
|
|
|
|
### Generate Admin Key
|
|
|
|
SSH into the server and run:
|
|
|
|
```bash
|
|
docker exec -it <backend-container-name> ./generate_admin_key.sh
|
|
```
|
|
|
|
Save the key. You'll use it to log into the Convex dashboard.
|
|
|
|
### 3. Configure Domains
|
|
|
|
Add **2 domains** in Coolify for the backend:
|
|
|
|
1. Backend API: `https://convex-backend.mentat.ovh:3210`
|
|
2. Actions proxy: `https://backend-site-xxx.mentat.ovh:3211`
|
|
|
|
### 4. Better Auth Env Vars
|
|
|
|
Set these on your Convex backend via CLI:
|
|
|
|
```bash
|
|
pnpm dlx convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)
|
|
pnpm dlx convex env set SITE_URL=http://localhost:3000
|
|
```
|
|
|
|
For production, `SITE_URL` should be your deployed frontend URL.
|
|
|
|
### 5. Resend Email Setup
|
|
|
|
Email is handled by Resend. The API key is **server-side only** and lives on Convex, not in `.env.local`.
|
|
|
|
1. Get your API key from [resend.com](https://resend.com)
|
|
2. Verify your domain in Resend and create a sender email (e.g. `noreply@yourdomain.com`)
|
|
3. Set Convex environment variables:
|
|
|
|
```bash
|
|
pnpm dlx convex env set RESEND_API_KEY=re_xxxxxxxxxxxxx
|
|
pnpm dlx convex env set RESEND_FROM_EMAIL=noreply@yourdomain.com
|
|
```
|
|
|
|
> These are read by `convex/lib/resend.ts` at runtime. Never commit them.
|
|
|
|
## Adding a New Project from Template
|
|
|
|
### Option A: Manual Clone (Private Forgejo)
|
|
|
|
```bash
|
|
git clone <forgejo-url>/convex-next-saas.git new-project
|
|
cd new-project
|
|
# Remove .git and re-init
|
|
git remote remove origin
|
|
git init
|
|
git remote add origin <new-project-url>
|
|
```
|
|
|
|
### Option B: Using the Init Script
|
|
|
|
```bash
|
|
node bin/init-template.mjs new-project
|
|
```
|
|
|
|
This will:
|
|
- Copy the template to `new-project/`
|
|
- Replace personal/project-specific strings with placeholders
|
|
- Initialize a fresh git repo
|
|
- Run `pnpm install`
|
|
|
|
**What gets replaced:**
|
|
|
|
| Original | Replaced With |
|
|
|----------|---------------|
|
|
| `convex-next-saas` | `<project-name>` |
|
|
| `SaaS Template` | `<project-name>` |
|
|
| `Create SaaS in a day!` | `<project-name> — built with convex-next-saas` |
|
|
| `https://convex-backend.mentat.ovh` | `https://your-convex-backend.example.com` |
|
|
| `https://convex-backend.mentat.ovh:3210` | `https://your-convex-backend.example.com:3210` |
|
|
| `https://backend-site-olnjg91x5ervt6j6owwgnlha.mentat.ovh` | `https://your-convex-site.example.com` |
|
|
| `https://backend-site-xxx.mentat.ovh:3211` | `https://your-convex-site.example.com:3211` |
|
|
| `self-hosted-convex\|01eea0ecf04bed0f70b73021564229f7f08eecff003f7294e5f9279faa4c19ffd5c501b0ac` | `self-hosted-convex\|YOUR_ADMIN_KEY` |
|
|
| `/home/nxtkofi/.config/tmuxinator/` | `~/.config/tmuxinator/` |
|
|
| `~/vaults/mentat/` | `~/workspace/` |
|
|
|
|
> **Note:** The script performs a simple string replacement across all text files. Review the output if you have customizations that might collide with these patterns.
|
|
|
|
## Common Issues
|
|
|
|
### `missing field functions` on `npx convex dev`
|
|
|
|
Your CLI and backend image versions differ. Check:
|
|
|
|
```bash
|
|
npx convex --version
|
|
# Compare with backend image tag in Coolify
|
|
```
|
|
|
|
Upgrade whichever is lower.
|
|
|
|
### Auth redirect loop
|
|
|
|
- Ensure `callbackURL` starts with `/`
|
|
- Ensure `sign-in` and `sign-up` pages are **not** protected by `isAuthenticated()`
|
|
- Check `NEXT_PUBLIC_SITE_URL` matches your actual URL
|
|
|
|
### Locale not switching
|
|
|
|
- Check `src/proxy.ts` matcher includes the route
|
|
- Check browser has `NEXT_LOCALE` cookie
|
|
- Ensure both `messages/en.json` and `messages/pl.json` exist and are valid JSON
|
|
|
|
### Build fails with Turbopack
|
|
|
|
You forgot `--webpack`:
|
|
|
|
```bash
|
|
# Wrong
|
|
pnpm build
|
|
|
|
# Right
|
|
# build script in package.json already does next build
|
|
# dev only:
|
|
pnpm dev --webpack
|
|
```
|
|
|
|
## Project Checklist
|
|
|
|
When starting a new project:
|
|
|
|
- [ ] Clone template / run init script
|
|
- [ ] Update `package.json` name
|
|
- [ ] Update `README.md` title and description
|
|
- [ ] Fill `.env.local`
|
|
- [ ] Deploy Convex backend on Coolify
|
|
- [ ] Set `BETTER_AUTH_SECRET` and `SITE_URL` on Convex
|
|
- [ ] Set `RESEND_API_KEY` and `RESEND_FROM_EMAIL` on Convex
|
|
- [ ] Update `src/app/layout.tsx` metadata
|
|
- [ ] Remove/replace placeholder content in `src/app/[locale]/page.tsx`
|
|
- [ ] Add project-specific routes to `src/lib/routes.ts`
|
|
- [ ] Run `pnpm lint` and `pnpm build` to verify
|
|
|
|
## PWA Lite
|
|
|
|
The template includes lightweight PWA installability out of the box. It is intentionally minimal and does **not** include offline caching, push notifications, or app-store wrappers.
|
|
|
|
### What is included
|
|
- `src/app/manifest.ts` — Web App Manifest with installability metadata
|
|
- `public/pwa/icon.svg` — template placeholder icon
|
|
- `public/pwa/maskable-icon.svg` — template placeholder maskable icon
|
|
- `public/pwa/apple-touch-icon.svg` — template placeholder Apple touch icon
|
|
- `src/app/layout.tsx` — PWA-relevant metadata (`applicationName`, `appleWebApp`, `icons`)
|
|
|
|
### What is NOT included
|
|
- Service worker or offline caching
|
|
- Push notifications
|
|
- Google Play TWA / Bubblewrap wrapper
|
|
- Apple App Store native wrapper
|
|
|
|
### Before shipping, customize these
|
|
| Field | File |
|
|
|-------|------|
|
|
| `name`, `short_name`, `description` | `src/app/manifest.ts` |
|
|
| `theme_color`, `background_color` | `src/app/manifest.ts` |
|
|
| `start_url`, `scope` | `src/app/manifest.ts` |
|
|
| Icon files | `public/pwa/` |
|
|
| `applicationName`, `appleWebApp.title` | `src/app/layout.tsx` |
|
|
|
|
> Replace the placeholder SVG icons with real branded assets before production. The template uses neutral `S` initials as a placeholder only.
|
|
|
|
### Locale behavior
|
|
The default manifest uses `start_url: '/'`. With `localePrefix: 'as-needed'` in `src/i18n/routing.ts`, Next.js/next-intl resolves the locale automatically when the app opens.
|
|
|
|
### Auth / cache note
|
|
Do not add aggressive caching for authenticated SaaS pages (dashboard, settings) unless you are deliberately designing offline behavior. The template intentionally skips the service worker to avoid accidental auth data leaks.
|
|
|
|
### Store distribution
|
|
- **Google Play**: requires a native/TWA wrapper such as Bubblewrap. The manifest alone is not enough.
|
|
- **Apple App Store**: usually rejects simple repackaged websites without native value. A PWA wrapper alone is not sufficient.
|
|
|
|
## Docker Deployment
|
|
|
|
The template includes a `Dockerfile` and `docker-compose.yml` for containerized deployment.
|
|
|
|
### Dockerfile
|
|
|
|
Multi-stage build optimized for production:
|
|
|
|
- **deps**: Installs dependencies
|
|
- **builder**: Builds the Next.js app
|
|
- **runner**: Minimal image with only production artifacts
|
|
|
|
```bash
|
|
# Build locally
|
|
docker build -t my-app .
|
|
|
|
# Run locally
|
|
docker run -p 3000:3000 --env-file .env.local my-app
|
|
```
|
|
|
|
### Docker Compose
|
|
|
|
Frontend-only deployment:
|
|
|
|
```bash
|
|
# Build and start Next.js app
|
|
docker compose up --build
|
|
```
|
|
|
|
Services:
|
|
|
|
| Service | Port | Description |
|
|
|---------|------|-------------|
|
|
| `app` | 3000 | Next.js application |
|
|
|
|
> **Note**: Convex backend runs separately. See [Coolify Deployment](#coolify-deployment) for Convex setup.
|
|
|
|
### Server Components in Docker
|
|
|
|
Next.js 16 App Router with Server Components (RSC) works identically in Docker:
|
|
|
|
- **Build time**: `next build` generates static pages + server bundles
|
|
- **Runtime**: `next start` serves both static and server-rendered content
|
|
- **No special config needed** — `output: 'standalone'` in `next.config.ts` handles everything
|
|
|
|
The Dockerfile uses `output: 'standalone'` which creates a self-contained server bundle. This means:
|
|
- Smaller image size (only production files)
|
|
- Faster startup
|
|
- No need for `node_modules` in final image
|
|
|
|
### Environment Variables
|
|
|
|
When running in Docker, pass env vars via:
|
|
|
|
1. `.env` file in the same directory as `docker-compose.yml`
|
|
2. `environment` section in `docker-compose.yml`
|
|
3. `-e` flags with `docker run`
|
|
|
|
Required variables:
|
|
|
|
```bash
|
|
NEXT_PUBLIC_SITE_URL=https://your-domain.com
|
|
NEXT_PUBLIC_CONVEX_URL=https://your-convex-backend.example.com
|
|
NEXT_PUBLIC_CONVEX_SITE_URL=https://your-convex-site.example.com
|
|
CONVEX_SELF_HOSTED_URL=https://your-convex-backend.example.com
|
|
CONVEX_SELF_HOSTED_ADMIN_KEY=your-admin-key
|
|
```
|
|
|
|
### Coolify Deployment
|
|
|
|
#### 1. Setup Two Environments
|
|
|
|
Create two services in Coolify:
|
|
|
|
| Service | Branch | Convex DB | Domain |
|
|
|---------|--------|-----------|--------|
|
|
| `my-app-prod` | `main` | Prod | `app.example.com` |
|
|
| `my-app-dev` | `dev` | Dev | `dev-app.example.com` |
|
|
|
|
#### 2. Configure Webhooks (Auto-Deploy)
|
|
|
|
In each Coolify service, copy the **Deploy Webhook URL** and add it to Forgejo Secrets:
|
|
|
|
```bash
|
|
# Forgejo → Repo Settings → Secrets
|
|
COOLIFY_PROD_WEBHOOK=https://coolify.example.com/webhooks/...
|
|
COOLIFY_DEV_WEBHOOK=https://coolify.example.com/webhooks/...
|
|
```
|
|
|
|
The `.forgejo/workflows/ci.yml` automatically triggers deploy on push:
|
|
- `main` branch → Prod webhook
|
|
- `dev` branch → Dev webhook
|
|
|
|
#### 3. Set Environment Variables
|
|
|
|
In each Coolify service, set env vars:
|
|
|
|
**Prod**:
|
|
```bash
|
|
NEXT_PUBLIC_SITE_URL=https://app.example.com
|
|
NEXT_PUBLIC_CONVEX_URL=https://convex-prod.example.com
|
|
NEXT_PUBLIC_CONVEX_SITE_URL=https://convex-site-prod.example.com
|
|
CONVEX_SELF_HOSTED_URL=https://convex-prod.example.com
|
|
CONVEX_SELF_HOSTED_ADMIN_KEY=prod-admin-key
|
|
```
|
|
|
|
**Dev**:
|
|
```bash
|
|
NEXT_PUBLIC_SITE_URL=https://dev-app.example.com
|
|
NEXT_PUBLIC_CONVEX_URL=https://convex-dev.example.com
|
|
NEXT_PUBLIC_CONVEX_SITE_URL=https://convex-site-dev.example.com
|
|
CONVEX_SELF_HOSTED_URL=https://convex-dev.example.com
|
|
CONVEX_SELF_HOSTED_ADMIN_KEY=dev-admin-key
|
|
```
|
|
|
|
#### 4. Local Development Connects to Dev
|
|
|
|
Your `.env.local` should point to the **dev** Convex:
|
|
|
|
```bash
|
|
CONVEX_SELF_HOSTED_URL='https://convex-dev.example.com'
|
|
CONVEX_SELF_HOSTED_ADMIN_KEY='dev-admin-key'
|
|
NEXT_PUBLIC_SITE_URL=http://localhost:3000
|
|
NEXT_PUBLIC_CONVEX_URL=https://convex-dev.example.com
|
|
NEXT_PUBLIC_CONVEX_SITE_URL=https://convex-site-dev.example.com
|
|
```
|
|
|
|
### Health Check
|
|
|
|
Verify the app is running:
|
|
|
|
```bash
|
|
curl http://localhost:3000
|
|
```
|