270 lines
8.3 KiB
Markdown
270 lines
8.3 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.
|
|
|
|
## Coolify Deployment (Convex Self-Hosted)
|
|
|
|
### 1. Deploy Convex Backend
|
|
|
|
On Coolify, create a new service using this 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:-}'
|
|
# ... (see README.md for full config)
|
|
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.
|
|
|
|
### 2. 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.
|