185 lines
7.4 KiB
Markdown
185 lines
7.4 KiB
Markdown
# cdrdpyj
|
|
|
|
A TypeScript **Turborepo** wiring an **Astro** frontend to a **Payload CMS**
|
|
backend, both running against a **self-hosted Supabase** stack (Postgres),
|
|
orchestrated from a single **docker-compose** file — with **[portless]** for
|
|
stable `*.localhost` dev URLs.
|
|
|
|
```
|
|
┌──────────────┐ REST /api ┌──────────────┐ SQL ┌───────────────────────────┐
|
|
│ Astro (web) │ ──────────────────────▶ │ Payload (cms)│ ─────────────────▶ │ Supabase (Postgres 17) │
|
|
│ SSR, :4321 │ │ Next, :3000 │ schema: payload │ db · studio · kong · │
|
|
│ web.localhost│ │ │ │ auth · rest · storage │
|
|
└──────────────┘ └──────────────┘ └───────────────────────────┘
|
|
▲ portless Studio dashboard :8000
|
|
```
|
|
|
|
- **apps/web** — Astro 5 (SSR via `@astrojs/node`). Reads posts from Payload's REST API.
|
|
- **apps/cms** — Payload 3 (on Next.js 16). Stores content in a dedicated `payload` schema in Postgres.
|
|
- **Supabase** — trimmed self-hosted stack: `db`, `studio`, `meta`, `kong`, `auth`, `rest`, `storage`, `imgproxy`.
|
|
- **packages/typescript-config** — shared `tsconfig` bases.
|
|
|
|
[portless]: https://portless.sh
|
|
|
|
---
|
|
|
|
## Prerequisites
|
|
|
|
| Tool | Version | Notes |
|
|
| ------- | ------- | ----- |
|
|
| Node | ≥ 24 | portless and Payload both require Node 24+ |
|
|
| pnpm | ≥ 9 | `corepack enable` or `npm i -g pnpm` |
|
|
| Docker | + Compose v2 | for the Supabase backend |
|
|
| portless| latest | `pnpm add -g portless` (optional but recommended) |
|
|
|
|
---
|
|
|
|
## Quick start
|
|
|
|
```bash
|
|
# 1. Configure — one env file drives the whole stack
|
|
cp .env.example .env
|
|
|
|
# 2. Install workspace deps
|
|
pnpm install
|
|
|
|
# 3. Start the Supabase backend (Postgres + Studio + Auth + REST + Storage)
|
|
docker compose up -d # first run pulls images; give it a minute
|
|
|
|
# 4. Run the apps (Astro + Payload) with hot reload
|
|
pnpm dev
|
|
```
|
|
|
|
Then open:
|
|
|
|
| What | URL |
|
|
| --------------- | ------------------------------------- |
|
|
| Astro frontend | http://localhost:4321 (or the portless URL below) |
|
|
| Payload admin | http://localhost:3000/admin |
|
|
| Supabase Studio | http://localhost:8000 (login: `supabase` / `this_password_is_insecure_and_should_be_updated`) |
|
|
| Postgres | `postgresql://postgres:postgres@localhost:5432/postgres` |
|
|
|
|
First time in Payload admin you'll create the initial admin user. Then add a
|
|
**Post** (give it a `slug`) and it appears on the Astro homepage.
|
|
|
|
---
|
|
|
|
## portless — stable `*.localhost` URLs
|
|
|
|
[portless] gives the frontend a memorable HTTPS URL instead of a port number,
|
|
and injects `$PORT` automatically.
|
|
|
|
```bash
|
|
pnpm add -g portless # once
|
|
pnpm dev # Astro is started via `portless web astro dev`
|
|
```
|
|
|
|
The web app is served at **https://web.localhost**. On first run portless
|
|
generates and trusts a local CA and binds to `:443` (may prompt for sudo).
|
|
|
|
> **Why only the frontend?** Payload stays on plain `http://localhost:3000` so
|
|
> Astro's server-side `fetch` to it uses plain HTTP — Node doesn't trust
|
|
> portless's local CA for server-to-server HTTPS. To also route Payload through
|
|
> portless (https://cms.localhost), run `pnpm --filter cms dev:portless` and set
|
|
> `NODE_EXTRA_CA_CERTS` to portless's CA so Astro can reach it over HTTPS.
|
|
|
|
Prefer no portless? Use `pnpm --filter web dev:plain` (plain `astro dev`).
|
|
|
|
---
|
|
|
|
## Everything in Docker (optional)
|
|
|
|
The apps also ship with Dockerfiles behind a compose `apps` profile:
|
|
|
|
```bash
|
|
docker compose --profile apps up -d --build
|
|
```
|
|
|
|
This runs Astro (`:4321`) and Payload (`:3000`) as containers alongside
|
|
Supabase. `DATABASE_URI` and the Astro→Payload URL are rewired to the internal
|
|
Docker network automatically. The recommended day-to-day loop is still
|
|
`docker compose up -d` (backend) + `pnpm dev` (apps) for hot reload.
|
|
|
|
---
|
|
|
|
## Scripts
|
|
|
|
Run from the repo root:
|
|
|
|
| Command | Does |
|
|
| ------------------------ | ---- |
|
|
| `pnpm dev` | Turbo runs both apps (Astro via portless, Payload plain) |
|
|
| `pnpm build` | Build all apps |
|
|
| `pnpm typecheck` | Type-check all apps |
|
|
| `pnpm supabase:up` / `:down` | Start / stop the Supabase backend |
|
|
| `pnpm supabase:logs` | Tail backend logs |
|
|
| `pnpm supabase:reset` | ⚠️ Stop and **delete** all Postgres data + volumes |
|
|
| `pnpm stack:up` / `:down`| Backend **and** apps in Docker (`apps` profile) |
|
|
|
|
Per-app: `pnpm --filter cms generate:types`, `pnpm --filter cms generate:importmap`.
|
|
|
|
---
|
|
|
|
## How configuration works
|
|
|
|
There is **one** `.env` at the repo root, shared by everything:
|
|
|
|
- **docker-compose** reads it natively.
|
|
- **Payload** loads it from the root in `apps/cms/src/payload.config.ts`.
|
|
- **Astro** loads it via `vite.envDir` pointed at the root; `PUBLIC_*` vars are
|
|
exposed to the browser.
|
|
|
|
The Supabase `ANON_KEY` / `SERVICE_ROLE_KEY` in `.env.example` are the standard
|
|
Supabase demo keys, signed with the matching demo `JWT_SECRET`. They work out of
|
|
the box for local dev. **Changing `JWT_SECRET` invalidates them** — regenerate
|
|
with Supabase's `generate-keys` steps if you do.
|
|
|
|
---
|
|
|
|
## Project structure
|
|
|
|
```
|
|
.
|
|
├── docker-compose.yml # single-file Supabase stack (+ apps profile)
|
|
├── .env.example # one source of truth for all config
|
|
├── turbo.json # task pipeline
|
|
├── apps/
|
|
│ ├── web/ # Astro SSR frontend
|
|
│ └── cms/ # Payload 3 (Next.js) CMS
|
|
├── packages/
|
|
│ └── typescript-config/ # shared tsconfig bases
|
|
└── supabase/
|
|
└── volumes/ # kong config + Postgres init SQL (roles, jwt, payload schema)
|
|
```
|
|
|
|
---
|
|
|
|
## Production notes
|
|
|
|
This scaffold is tuned for local development. Before deploying anywhere real:
|
|
|
|
- Regenerate **every** secret in `.env` (`POSTGRES_PASSWORD`, `JWT_SECRET` +
|
|
matching keys, `PAYLOAD_SECRET`, `DASHBOARD_PASSWORD`, `PG_META_CRYPTO_KEY`,
|
|
the S3 protocol keys).
|
|
- Put Kong / the apps behind TLS and real domains; set `SITE_URL`,
|
|
`API_EXTERNAL_URL`, `SUPABASE_PUBLIC_URL`, `PAYLOAD_PUBLIC_SERVER_URL`,
|
|
`PUBLIC_PAYLOAD_API_URL` accordingly.
|
|
- Switch Payload from dev "push" to committed migrations
|
|
(`pnpm --filter cms payload migrate:create`).
|
|
- Consider adding back the Supabase services this scaffold trims (Realtime,
|
|
Edge Functions, Supavisor pooler) if you need them — see
|
|
[Supabase self-hosting docs](https://supabase.com/docs/guides/self-hosting/docker).
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
- **Astro homepage shows "Couldn't reach the CMS"** — Payload isn't running or
|
|
the DB is down. Ensure `docker compose up -d` is healthy and `pnpm dev` shows
|
|
the `cms` server on `:3000`.
|
|
- **Payload can't connect to Postgres** — the DB exposes `:5432`; confirm
|
|
`docker compose ps` shows `cdrdpyj-db` healthy and `DATABASE_URI` matches.
|
|
- **Ports already in use** — `5432`, `8000`, `3000`, `4321` must be free (or
|
|
change them in `.env` / the app dev scripts).
|
|
```
|