Skip to content

Every surprise, in one list

If you only read one page, read this one. Each line was a real problem during the rebuild.

  • notFound() in a root layout under app/[segment] shows Next’s default 404; call it from the page and add not-found.tsx (The app).
  • Metadata images with a dynamic param need their own generateStaticParams, or they render on demand (The app).
  • Reading the current time during a prerender must be cached ('use cache') or deferred (The app).
  • Typed routes reject paths that exist only as rewrites; use as Route (The app).
  • An empty RESEND_API_KEY= line failed validation until “empty” was treated as “unset” (Contact form).
  • getByRole("alert") also matches Next’s route announcer (Tests).
  • Turnstile tokens are valid on the hostname and every subdomain; an exact hostname check would have rejected every www. visitor (Contact form).
  • An auto-reply that echoes visitor input is a spam relay from your domain (Contact form).
  • Docker-published ports bypass INPUT, ufw and firewalld; filter in DOCKER-USER (Firewall).
  • A final -j DROP in DOCKER-USER also blocks containers’ outbound connections — no certificates, no API calls (Firewall).
  • One Cloudflare range was missing from the allowlist (Firewall).
  • Trailing # comments on rules make iptables-restore fail; a rules file had never loaded (Firewall).
  • One misplaced field (passiveHealthCheck) made Traefik drop the whole dynamic config (Traefik).
  • Rate limits keyed on the connecting IP are shared by everyone behind Cloudflare (Traefik).
  • The shared security-headers middleware sent noindex (Traefik).
  • LOG_LEVEL=warn crash-loops socket-proxy v0.5; loadbalancer.server.strategy isn’t a field; traefik healthcheck needs its own --ping (Traefik).
  • make up starts Portainer, whose first visitor becomes admin; it also doesn’t include your own service files (Traefik, First deploy).
  • A pinned image (henrygd/beszel-hub) didn’t exist (Traefik).
  • .gitignore listed secrets one by one, so new ones weren’t ignored (Image and secrets).
  • The backup stack’s $$ escaping and RESTIC_REPO name meant it could never authenticate (Backups).
  • Let’s Encrypt staging certificates fail Cloudflare Full (strict); test staging on the server itself (Certificates).
  • An image built on Apple Silicon won’t run on an x86 server (Image and secrets).
  • The Gitea runner is now gitea/runner 5.x; act_runner 0.6 is the end of the old line (Runner).
  • Gitea’s registry rejects the job’s GITEA_TOKEN for docker login, even with packages: write; tokens need package scope, not repository (CI).
  • CI jobs can’t SSH into a server whose sshd is loopback-only — so the server pulls (Deploys).
  • This guide’s own first deploy shipped without search. Pagefind 1.6.0’s Linux binary needs AVX2; the server is an older Xeon (Ivy Bridge) without it, so the binary died with “Illegal instruction”, and astro build still exited 0. It built fine on a Mac, because emulation there supplies AVX2. The fixes: pin pagefind to 1.5.2 with npm overrides, and make npm run build fail if dist/pagefind/pagefind.js is missing. Check your CPU with grep -o -w avx2 /proc/cpuinfo | head -1 before trusting a prebuilt binary.
  • The server “with Traefik and Gitea on it” had neither running — only their data (Audit).
  • A long tar looked hung; it had finished (Audit).
  • Search results from a docs mirror can favour obsolete versions (Working docs-first).