docs: rewrite README and update DEVELOPMENT guide
Rewrite README.md as a clean template landing page. Update DEVELOPMENT.md with Resend setup instructions and expanded checklist. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
parent
a4e8ae34ed
commit
4ae60473b6
2 changed files with 64 additions and 139 deletions
|
|
@ -119,6 +119,21 @@ pnpm dlx convex env set SITE_URL=http://localhost:3000
|
||||||
|
|
||||||
For production, `SITE_URL` should be your deployed frontend URL.
|
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
|
## Adding a New Project from Template
|
||||||
|
|
||||||
### Option A: Manual Clone (Private Forgejo)
|
### Option A: Manual Clone (Private Forgejo)
|
||||||
|
|
@ -193,6 +208,7 @@ When starting a new project:
|
||||||
- [ ] Fill `.env.local`
|
- [ ] Fill `.env.local`
|
||||||
- [ ] Deploy Convex backend on Coolify
|
- [ ] Deploy Convex backend on Coolify
|
||||||
- [ ] Set `BETTER_AUTH_SECRET` and `SITE_URL` on Convex
|
- [ ] 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
|
- [ ] Update `src/app/layout.tsx` metadata
|
||||||
- [ ] Remove/replace placeholder content in `src/app/[locale]/page.tsx`
|
- [ ] Remove/replace placeholder content in `src/app/[locale]/page.tsx`
|
||||||
- [ ] Add project-specific routes to `src/lib/routes.ts`
|
- [ ] Add project-specific routes to `src/lib/routes.ts`
|
||||||
|
|
|
||||||
187
README.md
187
README.md
|
|
@ -1,160 +1,69 @@
|
||||||
# SaaS template
|
# convex-next-saas
|
||||||
|
|
||||||
This is not gonna make You rich, but it sure as hell will save you a lot of time!
|
A personal, opinionated SaaS template built for speed. Next.js 16 + Convex self-hosted + Better Auth + Resend.
|
||||||
|
|
||||||
This is production ready setup for Convex and Next SaaS.
|
> This template is tailored for self-hosted Convex on Coolify with a private Forgejo workflow. It's not trying to be a generic marketplace template — it's the setup I actually use to ship projects fast.
|
||||||
|
|
||||||
## Features
|
## Stack
|
||||||
|
|
||||||
- Auth
|
- **Frontend**: Next.js 16 (App Router), React 19, TypeScript 5, Tailwind CSS 4
|
||||||
- Stripe
|
- **Backend**: Convex self-hosted (Docker on Coolify)
|
||||||
- shadcn/create UI templte
|
- **Auth**: Better Auth (email/password, email verification, password reset)
|
||||||
- next intl
|
- **Email**: Resend (free tier: 3,000 emails/month)
|
||||||
- next themes
|
- **UI**: shadcn/ui (radix-nova), @hugeicons/react
|
||||||
- Backend/Frontend Tests
|
- **i18n**: next-intl v4 with locale routing (`/en`, `/pl`)
|
||||||
- Tmuxinator
|
- **State**: RSC + Client Components hybrid
|
||||||
- CICD
|
- **Validation**: Zod v4
|
||||||
- accurate instructions
|
- **Linting**: ESLint + oxlint
|
||||||
|
|
||||||
## Development process
|
## What's Included
|
||||||
|
|
||||||
### Environments
|
- [x] Email/password auth with HIBP password checking
|
||||||
|
- [x] Email verification flow
|
||||||
|
- [x] Forgot / reset password flow
|
||||||
|
- [x] Change password (authenticated)
|
||||||
|
- [x] Locale-based routing (EN / PL)
|
||||||
|
- [x] Theme switching (dark/light/system)
|
||||||
|
- [x] Protected routes with redirect + callbackURL
|
||||||
|
- [x] Runtime env validation (Zod)
|
||||||
|
- [x] GitHub Actions CI (lint + build)
|
||||||
|
- [x] Project init script (`bin/init-template.mjs`)
|
||||||
|
|
||||||
- Dev cloud environment - based on `develop` branch
|
## Quick Start
|
||||||
- Prod cloud environment - based on `main` branch
|
|
||||||
|
|
||||||
## Getting Started
|
|
||||||
|
|
||||||
First, run the development server:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
git clone <your-forgejo-repo> my-project
|
||||||
|
cd my-project
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
# Copy and fill env vars
|
||||||
|
cp .env.example .env.local
|
||||||
|
|
||||||
|
# Set up Convex backend (see DEVELOPMENT.md)
|
||||||
|
# Then:
|
||||||
pnpm dev --webpack
|
pnpm dev --webpack
|
||||||
```
|
```
|
||||||
|
|
||||||
> [!NOTE]
|
> ⚠️ **Always use `--webpack`** — Turbopack is broken in Next.js 16.2.1 (900% CPU spike).
|
||||||
> As of Next.js@16.2.1 turbopack is broken ant it spikes CPU usage by 900% and 8-9GiB of RAM usage, thus why we use webpack. Wait for 16.3.0 to migrate to turbopack
|
|
||||||
|
|
||||||
### Convex setup
|
## Project Bootstrap
|
||||||
|
|
||||||
[Source of truth](https://github.com/get-convex/convex-backend/blob/main/self-hosted/README.md)
|
```bash
|
||||||
|
node bin/init-template.mjs my-project
|
||||||
1. Go on Coolify, search for Convex and set it up.
|
cd my-project
|
||||||
|
# Fill .env.local, deploy Convex, done.
|
||||||
> [!IMPORTANT]
|
|
||||||
> Best case: [copy config from here](https://github.com/get-convex/convex-backend/blob/main/self-hosted/docker/docker-compose.yml), paste it instead of Coolify's docker-compose file, then go into environment variables, copy them, paste them and the new docker-compose into an AI agent and tell him to properly map the envs to docker-compose so it's ready to deploy on coolify.
|
|
||||||
|
|
||||||
This works for Convex 1.34.0 (CLI+backend/dashboard)
|
|
||||||
|
|
||||||
```
|
|
||||||
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:
|
|
||||||
APPLICATION_MAX_CONCURRENT_MUTATIONS: '${APPLICATION_MAX_CONCURRENT_MUTATIONS:-16}'
|
|
||||||
APPLICATION_MAX_CONCURRENT_NODE_ACTIONS: '${APPLICATION_MAX_CONCURRENT_NODE_ACTIONS:-16}'
|
|
||||||
APPLICATION_MAX_CONCURRENT_QUERIES: '${APPLICATION_MAX_CONCURRENT_QUERIES:-16}'
|
|
||||||
APPLICATION_MAX_CONCURRENT_V8_ACTIONS: '${APPLICATION_MAX_CONCURRENT_V8_ACTIONS:-16}'
|
|
||||||
INSTANCE_NAME: '${INSTANCE_NAME}'
|
|
||||||
INSTANCE_SECRET: '${INSTANCE_SECRET}'
|
|
||||||
CONVEX_CLOUD_ORIGIN: '${SERVICE_URL_BACKEND}'
|
|
||||||
CONVEX_SITE_ORIGIN: '${SERVICE_URL_BACKEND_SITE}'
|
|
||||||
DISABLE_METRICS_ENDPOINT: '${DISABLE_METRICS_ENDPOINT:-true}'
|
|
||||||
DOCUMENT_RETENTION_DELAY: '${DOCUMENT_RETENTION_DELAY:-172800}'
|
|
||||||
DO_NOT_REQUIRE_SSL: '${DO_NOT_REQUIRE_SSL:-true}'
|
|
||||||
DISABLE_BEACON: '${DISABLE_BEACON:-false}'
|
|
||||||
REDACT_LOGS_TO_CLIENT: '${REDACT_LOGS_TO_CLIENT:-false}'
|
|
||||||
RUST_LOG: '${RUST_LOG:-info}'
|
|
||||||
RUST_BACKTRACE: '${RUST_BACKTRACE:-0}'
|
|
||||||
ACTIONS_USER_TIMEOUT_SECS: '${ACTIONS_USER_TIMEOUT_SECS:-600}'
|
|
||||||
HTTP_SERVER_TIMEOUT_SECONDS: '${HTTP_SERVER_TIMEOUT_SECONDS:-300}'
|
|
||||||
CONVEX_RELEASE_VERSION_DEV: '${CONVEX_RELEASE_VERSION_DEV:-}'
|
|
||||||
DATABASE_URL: '${DATABASE_URL:-}'
|
|
||||||
POSTGRES_URL: '${POSTGRES_URL:-}'
|
|
||||||
MYSQL_URL: '${MYSQL_URL:-}'
|
|
||||||
AWS_REGION: '${AWS_REGION:-}'
|
|
||||||
AWS_ACCESS_KEY_ID: '${AWS_ACCESS_KEY_ID:-}'
|
|
||||||
AWS_SECRET_ACCESS_KEY: '${AWS_SECRET_ACCESS_KEY:-}'
|
|
||||||
AWS_SESSION_TOKEN: '${AWS_SESSION_TOKEN:-}'
|
|
||||||
AWS_S3_FORCE_PATH_STYLE: '${AWS_S3_FORCE_PATH_STYLE:-}'
|
|
||||||
AWS_S3_DISABLE_SSE: '${AWS_S3_DISABLE_SSE:-}'
|
|
||||||
AWS_S3_DISABLE_CHECKSUMS: '${AWS_S3_DISABLE_CHECKSUMS:-}'
|
|
||||||
S3_ENDPOINT_URL: '${S3_ENDPOINT_URL:-}'
|
|
||||||
S3_STORAGE_EXPORTS_BUCKET: '${S3_STORAGE_EXPORTS_BUCKET:-}'
|
|
||||||
S3_STORAGE_SNAPSHOT_IMPORTS_BUCKET: '${S3_STORAGE_SNAPSHOT_IMPORTS_BUCKET:-}'
|
|
||||||
S3_STORAGE_MODULES_BUCKET: '${S3_STORAGE_MODULES_BUCKET:-}'
|
|
||||||
S3_STORAGE_FILES_BUCKET: '${S3_STORAGE_FILES_BUCKET:-}'
|
|
||||||
S3_STORAGE_SEARCH_BUCKET: '${S3_STORAGE_SEARCH_BUCKET:-}'
|
|
||||||
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}'
|
|
||||||
NEXT_PUBLIC_LOAD_MONACO_INTERNALLY: '${NEXT_PUBLIC_LOAD_MONACO_INTERNALLY:-true}'
|
|
||||||
depends_on:
|
|
||||||
backend:
|
|
||||||
condition: service_healthy
|
|
||||||
volumes:
|
|
||||||
data: null
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Then make sure to update domains and env vars accordingly
|
## Docs
|
||||||
|
|
||||||
2. Deploy the service(s - dashboard and the db). It has built-in persistent storage
|
- [`AGENTS.md`](./AGENTS.md) — Architecture, conventions, and anti-patterns for AI agents
|
||||||
3. Connect to server with ssh, jump into a **backend** container with `docker exec -it <name> ./generate_admin_key.sh`
|
- [`DEVELOPMENT.md`](./DEVELOPMENT.md) — Local setup, Coolify deployment, troubleshooting
|
||||||
You'll receive Your admin key and You'll be able to log into dashboard
|
|
||||||
|
|
||||||
```
|
## Environments
|
||||||
Admin key:
|
|
||||||
self-hosted-convex|010...
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Now Your base is ready. Go to .env.local and finish setting up the connection.
|
- **Dev**: `develop` branch
|
||||||
5. Run `pnpm install` (only if Your hosted Convex image is latest, else use a proper version)
|
- **Prod**: `main` branch
|
||||||
6. After that You can run `npx convex dev` and You should be ready to go!
|
|
||||||
|
|
||||||
>[!TIP]
|
## License
|
||||||
> Remember to add 2 domains with ports in Your backend Coolify config:
|
|
||||||
>
|
|
||||||
> 1. Actual backend domain e.g `https://convex-backend.mentat.ovh:3210`
|
|
||||||
> 2. Backend actions domain e.g `https://backend-site-olnjg91x5ervt6j6owwgnlha.mentat.ovh:3211`
|
|
||||||
|
|
||||||
### Frontend setup
|
MIT — do whatever you want.
|
||||||
|
|
||||||
Time to pick a theme! Go to [shadcn/create](https://ui.shadcn.com/create), create a theme, and simply use the received command to init this theme in Your project!
|
|
||||||
|
|
||||||
### Better Auth
|
|
||||||
|
|
||||||
This project utilizes `BetterAuth`. To set it up:
|
|
||||||
|
|
||||||
1. `pnpm dlx convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)`
|
|
||||||
2. `pnpm dlx convex env set SITE_URL=http://localhost:3000`
|
|
||||||
|
|
||||||
#### Troubleshooting
|
|
||||||
|
|
||||||
```
|
|
||||||
dev/templates/convex-next-saas main !? ❯ npx convex dev
|
|
||||||
✖ Error: Unable to start push to https://backend-i8e44nvzhj8sxw4qm9tbu1f7.mentat.ovh
|
|
||||||
✖ Error fetching POST https://backend-i8e44nvzhj8sxw4qm9tbu1f7.mentat.ovh/api/deploy2/start_push 400 Bad Request: BadJsonBody: Failed to deserialize the JSON body into th
|
|
||||||
e target type: appDefinition: missing field `functions` at line 1 column 272745
|
|
||||||
```
|
|
||||||
|
|
||||||
**Solution** - ensure both CLI and convex self-hosted cloud images versions are the same. If not - I suggest upgrading whatever version is lower to be higher.
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue