Nav bar & layout consistency - Base.astro's `wide` layout now defaults on for every page (was opt-in), fixing the nav bar wrapping differently depending on page width - Icon buttons for search and theme toggle (magnifying glass, sun/moon), replacing the plain "Search" text link and the "◐" glyph - Search is now a Cmd/Ctrl+K modal over Pagefind (lazy-loaded on first open) instead of a separate /search/ page, matching the old site - Fixed the article page's TOC sitting with unwanted left padding relative to the header (grid was center-justified instead of start-justified) Smooth navigation - Added Astro's ClientRouter for animated page transitions - Every page-local script that only ran once at module load (three.js hero, minimap, comments, book-spine carousel, scroll-to-top, reading progress, theme toggle, Pagefind init) now re-initializes via astro:page-load, with AbortController-based cleanup so window/document listeners don't accumulate across client-side navigations Restored from the legacy Zola site - Reading progress bar (article pages) and scroll-to-top button - Share buttons (X, Bluesky, LinkedIn, Reddit, HN, email) on writings - Prev/next post navigation with the old label/title layout - Utterances comments, wired to andwati/andwati.com with dark/light theme sync - YouTube embeds: any plain <iframe src="...youtube-nocookie.com..."> is now auto-wrapped in a responsive 16:9 container, gets loading="lazy", and emits VideoObject JSON-LD — no shortcode needed - LaTeX: $inline$ and $$block$$ math renders to static HTML via KaTeX at build time (no client-side JS shipped for it) - Tags page redesigned as a centered, alphabetical tag cloud Removed the related-posts section per request. Accessibility & contrast - --accent (#d1502f) measured 4.0:1 on the light background, just under WCAG AA's 4.5:1 for normal text — darkened to #a84025 (5.7:1), plus matching light-mode-only fixes for the two hljs syntax colors and the tip/warning callout colors that had the same problem in light mode - Fixed heading-order violations (h1 straight to h3 with nothing at h2) across the homepage, writings/portfolio/bookshelf/blogs lists, and tag pages — introduced a shared .entry-title class for the h2 list titles - Fixed inconsistent title-link underline colors: a plain <a> carries its own default underline (in its own text color) instead of matching its parent heading's accent-colored one — h1/h2/h3 anchors now explicitly inherit the heading's decoration - Bookshelf spine colors are generated per-title from a hash; the lightness-based text-color heuristic could still produce sub-4.5:1 contrast for some hues (e.g. green) — now computes real WCAG contrast and adjusts Performance - The three.js homepage hero was the single largest main-thread cost site-wide (dragging that page's Lighthouse performance score to the low 80s while every other page scored 95+): three.js is now dynamically imported only once the browser is idle (WebGL-support check split into its own tiny module so unsupported browsers never fetch it at all), the render loop is capped to ~30fps, and hover raycasting only runs on an actual pointer move instead of every frame SEO & structured data - Self-referential canonical links site-wide (with override support for posts first published elsewhere) - Sitewide WebSite/Person JSON-LD, plus per-article BlogPosting, BreadcrumbList, and (when applicable) VideoObject JSON-LD - robots.txt explicitly welcomes AI/LLM crawlers (GPTBot, ClaudeBot, PerplexityBot, Google-Extended, CCBot, etc.) and points at llms.txt - Added an Atom feed (RSS and JSON Feed already existed) - Sitemap now sets <lastmod> per entry from its own date/updated frontmatter instead of leaving every URL at build time Analytics & comments infra - Self-hosted Umami wiring via PUBLIC_UMAMI_SCRIPT_URL/WEBSITE_ID (apps/site/.env.example), no-ops until both are set - sws.toml's CSP updated to allow utteranc.es and analytics.andwati.com — without this both were silently blocked in production Lighthouse CI - @lhci/cli wired up against the built site (mobile + desktop configs), `pnpm run lighthouse`, and a GitHub Actions workflow to run it on every push/PR — reporting only for now, no failing thresholds
3.8 KiB
Dokploy deployment
The production deployment is one Dokploy Compose application with two services:
sitebuilds the Astro app and serves its static output withstatic-web-server.cmsruns Strapi with persistent SQLite/admin data and a persistent Git checkout.
Markdown remains the source of truth. A Strapi edit writes into the checkout, commits and pushes the changed Markdown (plus a generated bookshelf cover when applicable), then calls Dokploy's deploy webhook. The redeploy builds Astro from that new commit.
GitHub token
Create a fine-grained personal access token restricted to the
andwati/andwati.com repository with:
- Repository contents: read and write
- Metadata: read (automatically included)
The configured publishing branch must accept direct pushes from the token.
Store the token only in Dokploy as GITHUB_TOKEN.
Dokploy application
- Create a Compose application from this repository and select
docker-compose.prod.yml. - Configure its source branch to match
CONTENT_GIT_BRANCH(normallymain). - Route the public site domain to the
siteservice on port80. - Route the CMS domain (for example
cms.andwati.com) to thecmsservice on port1337. - Copy the application's deploy webhook URL into
DOKPLOY_DEPLOY_WEBHOOK_URL. - Add the environment variables below and deploy.
Required secrets:
CMS_PUBLIC_URL=https://cms.andwati.com
APP_KEYS=<four comma-separated random values>
API_TOKEN_SALT=<random value>
ADMIN_JWT_SECRET=<random value>
TRANSFER_TOKEN_SALT=<random value>
JWT_SECRET=<random value>
ENCRYPTION_KEY=<random value>
GITHUB_TOKEN=<fine-grained GitHub token>
DOKPLOY_DEPLOY_WEBHOOK_URL=<Dokploy Compose deploy webhook>
Optional publishing settings:
CONTENT_GIT_REPOSITORY=andwati/andwati.com
CONTENT_GIT_BRANCH=main
CONTENT_GIT_COMMIT_NAME="andwati.com CMS"
[email protected]
Site analytics (read at build time by apps/site, so these must be set on
the site service before it builds, not just at runtime):
PUBLIC_UMAMI_SCRIPT_URL=https://analytics.andwati.com/script.js
PUBLIC_UMAMI_WEBSITE_ID=9eef8fba-c932-4488-ae23-6b84553e8c29
Analytics.astro silently no-ops if either is unset — safe to deploy without
them, but analytics won't record anything until both are present at build
time. sws.toml's CSP already allows analytics.andwati.com and
utteranc.es (comments); if the Umami host ever changes, update both that
CSP and these two vars together, or the tracker will be silently blocked in
production instead of erroring visibly.
Generate each random secret independently; do not reuse the local development
values from docker-compose.yml.
Persistent data
The production Compose file declares three named volumes:
cms_databasekeeps Strapi's admin users, API tokens, and SQLite cache.cms_uploadskeeps files uploaded through Strapi.content_checkoutkeeps the Git working tree used for publishing.
Back up cms_database and cms_uploads. The content checkout is recoverable
from Git, but retaining it protects an edit if the process stops between the
filesystem write and Git push.
On startup, the CMS fast-forwards a clean checkout to the configured branch. If it finds uncommitted files, it preserves them and starts without pulling so an interrupted edit is not overwritten. Resolve and push those files from the volume before forcing a reset.
Local validation
Provide temporary values for required interpolation variables, then validate the production manifest:
docker compose --env-file .env.production \
-f docker-compose.prod.yml config
docker compose --env-file .env.production \
-f docker-compose.prod.yml build
The existing docker-compose.yml remains the local Strapi development setup.