# SoftVowels — project conventions A monorepo with two apps: ``` apps/web Astro 5 (static site, Tailwind v4) apps/cms Payload 3 (admin UI + REST/GraphQL, Next.js 15) ``` ## Source paths - `apps/web/src/components/{layout,home,article,category,search,newsletter,ui}/` — components - `apps/web/src/layouts/` — page layouts - `apps/web/src/lib/` — Payload client, types, SEO, formatting - `apps/web/src/styles/` — `global.css` (Tailwind v4 `@theme` tokens) and `prose.css` - `apps/web/public/fonts/` — drop `Geist-Variable.woff2` + `GeistMono-Variable.woff2` here - `apps/cms/src/collections/` — Payload collections - `apps/cms/src/hooks/` — Payload hooks - `apps/cms/src/lib/` — Payload helpers (e.g. `withRevalidate`) ## Code style - All component files are `.astro` single-file components. Frontmatter (between `---`) is TypeScript. - No comments in code unless explicitly requested. - Tailwind v4 tokens live in CSS (`@theme` in `global.css`). Do not create a `tailwind.config.*` file. - Use `icon` (`astro-icon`) with names like `material-symbols:memory`. Self-host icon font woff2 under `public/icons/material-symbols-outlined.woff2` and wire it via `@font-face` in `global.css` (already declared; add the binary file when ready). - Prefer `border border-border-subtle rounded-md` over arbitrary values to stay on the 4px baseline. - For code blocks in MDX / Lexical output, the article body uses `prose-article` (see `prose.css`). ## Lint / typecheck (web app) ```bash cd apps/web npx astro check npx prettier --check "src/**/*.{ts,astro,css}" ``` There is no separate ESLint run; the project's quality bar is enforced by `astro check` and Prettier. ## Local development (optional, not required for homeserver deploy) If you have Node + pnpm on your dev machine: ```bash pnpm install pnpm dev:web # Astro on :4321 pnpm dev:cms # Payload on :3000 ``` The homeserver deploy does **not** require Node or pnpm on the host — Docker builds both images from source. ## Build outputs - `apps/web/dist/` is generated by `astro build` inside the `softvowels-web` Docker image. Baked into the image at `/usr/share/nginx/html/`. - `apps/cms/.next/standalone/` is generated by `next build` (with `output: 'standalone'`) inside the `softvowels-cms` Docker image. Baked into the image and run via `node server.js`. - `apps/cms/media/` (uploaded files) lives in a named Docker volume (`softvowels-cms-uploads`), not the image. ## Deploy (homeserver, Docker only) The host needs Docker and Docker Compose v2. Nothing else. ```bash # 1. Get the code on the server git clone /opt/softvowels cd /opt/softvowels # 2. Configure environment cp .env.example .env $EDITOR .env # fill in POSTGRES_PASSWORD, PAYLOAD_SECRET, hostnames # 3. Build and start docker compose up -d --build # 4. Watch the first boot docker compose logs -f softvowels-cms # On first run, Payload prints a one-time URL for creating the admin # account. Open it, then visit https://cms.softvowels.example/admin. ``` ### Updating the site (no webhook yet) Until the webhook is in place, each site update requires: ```bash cd /opt/softvowels git pull docker compose build softvowels-web docker compose up -d softvowels-web ``` That's the entire "redeploy" for a content change. The CMS does not need rebuilding for content changes — only the web image. ## Nginx Proxy Manager setup Add two proxied hosts in NPM: | Domain | Forward to | Notes | |---|---|---| | `softvowels.example` | `softvowels-web:80` (http) | Force SSL, HSTS, HTTP/2, websockets off | | `cms.softvowels.example` | `softvowels-cms:3000` (http) | Force SSL, HSTS, websockets on (Payload admin uses them) | NPM must be able to resolve `softvowels-web` and `softvowels-cms`. The `nginx_proxy_manager` network is `external: true` in `docker-compose.yml`; the easiest way to make NPM see the containers is to run NPM on the same host and add the network to its container with: ```bash docker network connect nginx_proxy_manager ``` ## Self-hosting fonts Drop these two files into `apps/web/public/fonts/` (before `docker compose build`): - `Geist-Variable.woff2` (https://github.com/vercel/geist-font — rename `GeistVF.woff2`) - `GeistMono-Variable.woff2` (rename `GeistMonoVF.woff2`) The `@font-face` rules are already declared in `global.css`. ## Environment Root `.env` (used by `docker compose`): ``` POSTGRES_PASSWORD= PAYLOAD_SECRET= WEB_HOSTNAME= # e.g. softvowels.example CMS_HOSTNAME= # e.g. cms.softvowels.example ``` `REVALIDATE_TOKEN` was removed when the webhook was deferred. Re-add it to `.env` and `docker-compose.yml` when the on-publish rebuild is wired back in.