Initial commit: BlueCap Strategies Astro site
Static Astro 5 site for BlueCap Strategies with exact live-site content parity, deployed to k3s via GitOps (ArgoCD). - Page copy in Markdown content collections (services, focus-areas, pages, insights) with a Zod SEO schema enforced at build time - Homepage + About restored to exact live copy; real live-site imagery - Build-time SEO linter (scripts/seo-lint.mjs) and Playwright e2e suite - Multi-stage Dockerfile (nginx serves dist/) and Kustomize manifests (k8s/) - Per-agent robots.txt; config-driven PostHog + Umami analytics scaffold - ADRs and engineering docs under docs/ Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
commit
cd91c4b97c
92 files changed
+10246
No files matched your search
@@ -0,0 +1,25 @@
|
||||
# ADR-0001: GitOps deployment via ArgoCD (not Flux)
|
||||
|
||||
**Status:** Accepted — 2026-07-15
|
||||
|
||||
## Context
|
||||
|
||||
The site deploys to the homelab k3s cluster. GitHub must never receive kubeconfig or direct
|
||||
cluster access. An earlier plan specified Flux, but Flux was never implemented, and the homelab
|
||||
**already runs ArgoCD** (namespace `argocd`, active 21d+) managing other workloads.
|
||||
|
||||
## Decision
|
||||
|
||||
Use **ArgoCD** as the GitOps reconciler. Model: local edit → GitHub Actions builds + pushes image
|
||||
to GHCR (`<git-sha>` immutable tags) → CI commits the tag into `k8s/` → ArgoCD auto-syncs the
|
||||
Kustomize manifests → k3s rolls the Deployment → Traefik serves. Manifests stay as plain Kustomize
|
||||
under `k8s/` (ArgoCD consumes `k8s/kustomization.yaml` natively; no Helm chart).
|
||||
|
||||
## Consequences
|
||||
|
||||
- No second GitOps controller to run; reuses existing, understood infra.
|
||||
- Cluster pulls from Git + GHCR; GitHub never touches the cluster.
|
||||
- Requires: `git init` + GitHub remote (not yet done), a GHCR image path, and an ArgoCD
|
||||
`Application` CR (direct or via the homelab app-of-apps).
|
||||
- Rollback = `git revert` + push, or ArgoCD history rollback.
|
||||
- Full detail in `docs/gitops-deployment-strategy.md`.
|
||||
@@ -0,0 +1,30 @@
|
||||
# ADR-0002: Page copy in Astro Content Collections (Markdown)
|
||||
|
||||
**Status:** Accepted — 2026-07-15
|
||||
|
||||
## Context
|
||||
|
||||
Body copy currently lives inside `.ts` files (`src/data/services.ts`, `src/data/focusAreas.ts`)
|
||||
as quoted string properties — painful to edit, no spellcheck/preview, and prose is trapped in
|
||||
code. Separately, the rebuild paraphrased much of the live-site copy, and Phase 2 requires
|
||||
restoring exact copy (see `docs/parity-checklist.md`). Doing both at once avoids a double
|
||||
migration.
|
||||
|
||||
## Decision
|
||||
|
||||
Move prose-heavy page copy **out of `.ts` and into Astro Content Collections as Markdown**, with
|
||||
typed frontmatter for structured/SEO fields. Split of responsibility:
|
||||
|
||||
- **Prose** (service/focus/about body, insights) → Markdown body rendered through a shared layout.
|
||||
- **Structured/visual data** (metrics grid, nav, service icon+blurb cards, CTAs) → typed
|
||||
frontmatter or kept in `.ts` where it drives layout/components.
|
||||
|
||||
Exact-copy restoration (Phase 2) lands directly in this format, not into the old `.ts` shape.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Editing copy no longer means editing code; enables an eventual owner-editing workflow.
|
||||
- Consistent rendering/flow via one styled layout (reduces, not increases, formatting risk).
|
||||
- Requires content collections for `services`, `focusAreas`, and standalone `pages`, plus
|
||||
refactoring the `[slug]` routes to read from collections.
|
||||
- Pairs with ADR-0003 (the collection schema encodes SEO rules).
|
||||
@@ -0,0 +1,30 @@
|
||||
# ADR-0003: Enforce SEO rules with Zod + build linter (not Pydantic)
|
||||
|
||||
**Status:** Accepted — 2026-07-15
|
||||
|
||||
## Context
|
||||
|
||||
We want SEO/AEO rules (title/description length, canonical, OG image, structured data, single H1,
|
||||
alt text) to be *enforced*, not aspirational. The question of a Pydantic-style validator came up,
|
||||
but this is a TypeScript/Astro build — Pydantic is Python and would mean bolting a foreign runtime
|
||||
onto a JS pipeline.
|
||||
|
||||
## Decision
|
||||
|
||||
Enforce in two layers, both native to the stack:
|
||||
|
||||
1. **Zod schema** on content-collection frontmatter (Astro Content Collections already run on Zod).
|
||||
Encodes: title ≤ 55 chars, description 120–158, valid canonical, required OG image, `noindex`
|
||||
flag, `primaryTopic`. Violations **fail `npm run build`**.
|
||||
2. **Build-time SEO linter** (`scripts/seo-lint.mjs`) over `dist/**/*.html` for rules Zod can't see
|
||||
in frontmatter: exactly one `<h1>`, heading order, canonical/title/description present, non-empty
|
||||
`alt` on every `<img>`, every JSON-LD block parses. Runs in CI (and locally via `npm run seo:lint`).
|
||||
|
||||
Do **not** introduce Python/Pydantic.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Every commit is validated before ArgoCD deploys; "the rules are followed" is guaranteed by CI.
|
||||
- Rules live with the content schema and the build, in one language.
|
||||
- Requires the content collections from ADR-0002 to carry the shared `seo` schema.
|
||||
- Detailed rules in `docs/seo-aeo-optimization.md` §1.
|
||||
@@ -0,0 +1,30 @@
|
||||
# ADR-0004: Reuse the monylog stack for ops monitoring (not Uptime Kuma)
|
||||
|
||||
**Status:** Accepted — 2026-07-15
|
||||
|
||||
## Context
|
||||
|
||||
An earlier note proposed deploying Uptime Kuma in-cluster. Inspection showed the homelab already
|
||||
runs **monylog** (`10.66.15.21`, Ansible role `pglta`): a full self-hosted LGTM + Alertmanager
|
||||
stack (Prometheus, Grafana, Loki, Tempo, Alertmanager → Pushover/ntfy → phone), deliberately
|
||||
**external to k8s** so it alerts even when the cluster is down. Grafana Alloy (k3s DaemonSet)
|
||||
already remote-writes cluster metrics (incl. kube-state-metrics) and ships all pod logs to
|
||||
monylog Loki.
|
||||
|
||||
## Decision
|
||||
|
||||
**Reuse monylog; do not deploy Uptime Kuma or any new alerting channel.** BlueCap pod/deployment
|
||||
metrics and nginx logs already flow in. Add only:
|
||||
|
||||
1. `blackbox_exporter` to the monylog compose + a Prometheus scrape job probing the in-cluster
|
||||
Service (app health) and the public URL (edge-path uptime + TLS expiry).
|
||||
2. A `web-bluecap.yml` alert rules file (SiteDown, CertExpiringSoon <14d, High5xx,
|
||||
DeploymentDegraded, PodRestarting).
|
||||
3. A `service: bluecap` label so Alertmanager routes to existing Pushover; a Grafana dashboard.
|
||||
|
||||
## Consequences
|
||||
|
||||
- No duplicate monitoring/alerting infra; one pane of glass, alerts already reach the phone.
|
||||
- Changes land in the Homelab Ansible repo (`roles/monylog`), not this repo.
|
||||
- monylog being off-cluster gives a genuine external vantage for public-URL probing.
|
||||
- Detail in `docs/seo-aeo-optimization.md` §6a and `docs/gitops-deployment-strategy.md` §4.3.
|
||||
@@ -0,0 +1,31 @@
|
||||
# ADR-0005: Visitor analytics — PostHog + Umami side-by-side trial
|
||||
|
||||
**Status:** Accepted — 2026-07-15
|
||||
|
||||
## Context
|
||||
|
||||
The owner leans toward richer data (PostHog: autocapture, funnels, session replay) but the site is
|
||||
a low-traffic B2B advisory brochure with a Digital-ESG / low-energy positioning, and self-hosting
|
||||
PostHog is resource-heavy. Rather than decide in the abstract, the owner chose to **trial both**
|
||||
and compare. This is product/visitor analytics — distinct from the monylog ops telemetry (ADR-0004).
|
||||
|
||||
## Decision
|
||||
|
||||
Run **PostHog and Umami side-by-side initially** to evaluate which fits. Recommended trial setup:
|
||||
|
||||
- **PostHog — Cloud free tier** (1M events/mo) rather than self-host, to avoid loading the homelab
|
||||
during evaluation; reverse-proxy ingest through our domain to reduce ad-blocker loss.
|
||||
- **Umami — self-hosted** in k3s (lightweight, cookieless, no consent banner).
|
||||
|
||||
Both are wired through a single config-driven `Analytics.astro` component in `BaseLayout`, gated by
|
||||
env vars, so either can be toggled and a winner kept without code churn. Track the same event set
|
||||
in both (consultation-CTA clicks, per-service/focus CTA source, insights reads, contact submits).
|
||||
|
||||
## Consequences
|
||||
|
||||
- Temporary dual instrumentation (slightly more script weight during the trial).
|
||||
- **Privacy/consent must cover the superset:** PostHog (esp. session replay) likely triggers
|
||||
cookie-consent + privacy-policy obligations; add these before enabling PostHog in production.
|
||||
- Decision criteria: insight value vs. weight/consent/ESG fit. Revisit with a follow-up ADR naming
|
||||
the winner and the retirement of the other.
|
||||
- Detail in `docs/seo-aeo-optimization.md` §6b.
|
||||
@@ -0,0 +1,26 @@
|
||||
# ADR-0006: Launch a blog at /insights/
|
||||
|
||||
**Status:** Accepted — 2026-07-15
|
||||
|
||||
## Context
|
||||
|
||||
The live WordPress site has **no published blog** (0 posts, 0 `project` entries) but does have an
|
||||
**unpublished, empty "Insights & Resources" draft** (`page_id=303`) — a blog scaffolded but never
|
||||
launched. The rebuild already has an `/insights/` section (routes, RSS, content collection, one
|
||||
example draft). The parity plan and SEO/AEO plan both identify thought leadership as a core
|
||||
authority/GEO asset.
|
||||
|
||||
## Decision
|
||||
|
||||
**Launch the blog at `/insights/`** in the rebuild. Keep the existing Astro content-collection
|
||||
scaffold (Markdown/MDX, RSS, tags, canonical for cross-posts). Publish only **real,
|
||||
owner-reviewed** content — no fabricated or unreviewed AI-generated articles. Candidate first
|
||||
pieces: PFAS remediation as a capital-formation problem; scaling marine conservation finance;
|
||||
making island/coastal infrastructure investable.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Gives AEO/GEO a surface to be cited from; `Article`/`BlogPosting` schema per SEO plan §3.
|
||||
- Content is owner-gated — the section can ship empty or with 1–2 real posts; do not backfill with
|
||||
placeholder articles.
|
||||
- Insights carry `publishDate`/`modifiedDate`; RSS and sitemap `lastmod` derive from these.
|
||||
@@ -0,0 +1,16 @@
|
||||
# Architecture Decision Records
|
||||
|
||||
Short, immutable records of significant decisions on the BlueCap Strategies rebuild. Each ADR
|
||||
captures the context, the decision, and its consequences at a point in time. Supersede rather than
|
||||
edit: to change a decision, add a new ADR and mark the old one `Superseded by ADR-XXXX`.
|
||||
|
||||
Format: Status · Context · Decision · Consequences. Statuses: Proposed · Accepted · Superseded.
|
||||
|
||||
| ADR | Title | Status |
|
||||
|-----|-------|--------|
|
||||
| [0001](0001-gitops-with-argocd.md) | GitOps deployment via ArgoCD (not Flux) | Accepted |
|
||||
| [0002](0002-page-copy-in-content-collections.md) | Page copy in Astro Content Collections (Markdown) | Accepted |
|
||||
| [0003](0003-seo-enforcement-with-zod.md) | Enforce SEO rules with Zod + build linter (not Pydantic) | Accepted |
|
||||
| [0004](0004-reuse-monylog-for-monitoring.md) | Reuse the monylog stack for ops monitoring (not Uptime Kuma) | Accepted |
|
||||
| [0005](0005-analytics-posthog-and-umami-trial.md) | Visitor analytics: PostHog + Umami side-by-side trial | Accepted |
|
||||
| [0006](0006-launch-insights-blog.md) | Launch a blog at /insights/ | Accepted |
|
||||
@@ -0,0 +1,78 @@
|
||||
# Content Deviations
|
||||
|
||||
Intentional differences between the live WordPress site (`docs/live-site-content-inventory.md`)
|
||||
and the Astro rebuild, logged per the parity plan. Updated **2026-07-15** after the copy
|
||||
migration to Markdown collections (ADR-0002).
|
||||
|
||||
## Service & focus-area detail pages — exact copy restored
|
||||
|
||||
- The 8 service detail pages and 4 focus-area detail pages now carry **verbatim live-site copy** in
|
||||
`src/content/services/*.md` and `src/content/focus-areas/*.md`. Each service page: H1 (service
|
||||
name) → H2 (exact tagline) → the exact three body paragraphs, matching the live structure.
|
||||
- **Removed invented content** from the earlier rebuild: the paraphrased `sections[]` sub-headings
|
||||
and the "Typical outputs" / `outcomes[]` lists were **not present on the live site** and have been
|
||||
dropped to honor the no-invention rule. They may return later as owner-approved "Typical outputs"
|
||||
modules (parity plan Phase 5).
|
||||
- **Focus-area detail pages** use the exact single paragraph from the homepage "Our Core Focus
|
||||
Areas" section — the only live copy that exists for these (the live `/focus-areas/` page is empty).
|
||||
No additional copy was invented.
|
||||
|
||||
## Metadata (not visible body copy)
|
||||
|
||||
- **SEO meta descriptions** (`description` frontmatter, 120–158 chars) are newly written marketing
|
||||
metadata, not live body copy. Paraphrase is acceptable for meta descriptions; they are enforced
|
||||
by the Zod schema (ADR-0003).
|
||||
- `summary` frontmatter reuses the **exact** live `/services/` overview paragraph (services) and the
|
||||
**exact** homepage focus paragraph (focus areas); it drives the overview and homepage cards.
|
||||
|
||||
## Typography
|
||||
|
||||
- Curly apostrophes/quotes from WordPress were normalized to **straight** apostrophes in the Markdown
|
||||
bodies (e.g., "client's" not "client's"). Meaning and wording are unchanged.
|
||||
|
||||
## Homepage — exact copy restored (2026-07-15)
|
||||
|
||||
- `src/pages/index.astro` now carries **exact live copy**: hero paragraph, the full "How We Work"
|
||||
section (intro + Increasing the Supply of Capital + Bridging Supply and Demand + Cultivating
|
||||
Capital-Ready Initiatives incl. the "step in as sponsor" paragraph), the About teaser, the four
|
||||
metrics (27 Years / $10+ billion / $3.5+ billion / 275+ Projects and Initiatives, with exact
|
||||
supporting text), and the Core Focus Areas intro.
|
||||
- **Additive (not live copy):** the "Our Services" section keeps a short connective subhead +
|
||||
intro sentence (the live homepage lists only service titles). No fabricated claims.
|
||||
- **Default OG / hero fallback images** repointed to real photos (`img_0067`); the contact hero uses
|
||||
`img_0058`. The four generic placeholder images were **deleted** from `public/assets/`.
|
||||
- **Omitted defect:** the live "Follow Our Quest" lorem-ipsum block remains removed.
|
||||
- H1 wording is exact; live renders RESTORATION/PRESERVATION/RESPONSIBLE USE in all-caps, the
|
||||
rebuild uses sentence case (typographic only).
|
||||
|
||||
## About Us — exact copy restored + migrated to Markdown (2026-07-15)
|
||||
|
||||
- Migrated to `src/content/pages/about.md` (ADR-0002) with **verbatim** copy for all ten sections
|
||||
and every paragraph. The sticky in-page nav is derived from the h2 headings (restores the live
|
||||
anchor nav). The live "andrestore" typo is corrected to "and restore".
|
||||
- Added hero heading ("Finance, innovation, and stewardship…") — the live About page has no hero
|
||||
heading; this is an additive structural heading, not body copy.
|
||||
|
||||
## Imagery — real live-site assets imported (2026-07-15)
|
||||
|
||||
- Real assets downloaded to `public/assets/live-site/` (+ `/icons`). The real logo replaced the
|
||||
placeholder `public/assets/bluecap-logo.png`. Homepage hero (`img_0067`), homepage About band
|
||||
(`img_0053`), About hero (`img_0062`), Services hero (`img_0075`), Focus/detail heroes
|
||||
(`img_0070`) now use real photography. The 8 service cards render their real icons (decorative,
|
||||
`aria-hidden`).
|
||||
- `IMG_0075.webp` was JPEG-mislabeled on the live site; saved as `img_0075.jpg`.
|
||||
|
||||
## About Us imagery — interleaved (2026-07-15)
|
||||
|
||||
- Eight real live-site photos are now interleaved through the About sections (`img1`, `img_0055`,
|
||||
`img_0054`, `img_0072`, `img_0053`, `img_0058`, `img_0075`, `img_0070`), plus `img_0062` as the
|
||||
hero — matching the live page's ~9-photo visual rhythm. Alt text is descriptive (live alts were
|
||||
empty). Order approximates the live placement; exact per-section pairing is best-effort.
|
||||
|
||||
## Known placeholders / not-yet-restored (tracked in parity-checklist.md)
|
||||
|
||||
- In-body About images are referenced by URL from Markdown, so they ship without intrinsic
|
||||
width/height (minor CLS); revisit if a Lighthouse CLS budget requires it.
|
||||
- Per-page **1200×630 OG images** (SEO plan §2) still to be generated; pages currently share the
|
||||
`img_0067` default.
|
||||
- Contact/defect items unchanged (see `bcs-website-owner-decisions`).
|
||||
@@ -0,0 +1,83 @@
|
||||
# Deployment
|
||||
|
||||
## Local Commands
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev
|
||||
npm run typecheck
|
||||
npm run build
|
||||
npm run preview
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Set these at build time:
|
||||
|
||||
```bash
|
||||
PUBLIC_SITE_URL=https://www.bluecapstrategies.com
|
||||
PUBLIC_CONTACT_FORM_ENDPOINT=https://your-form-endpoint.example
|
||||
```
|
||||
|
||||
`PUBLIC_CONTACT_FORM_ENDPOINT` can point to Formspree, a webhook, ntfy, or a self-hosted form
|
||||
receiver. Without it, the contact form validates fields and opens an email draft to
|
||||
`info@bluecapstrategies.com`.
|
||||
|
||||
## Container
|
||||
|
||||
```bash
|
||||
docker build -t bluecap-strategies-website:latest .
|
||||
docker run --rm -p 8080:80 bluecap-strategies-website:latest
|
||||
```
|
||||
|
||||
## k3s
|
||||
|
||||
Production deployment should use the pull-based GitOps model documented in
|
||||
`docs/gitops-deployment-strategy.md`.
|
||||
|
||||
The intended flow is:
|
||||
|
||||
```text
|
||||
local edit -> git push -> GitHub Actions test/build/publish -> Git manifest image tag update -> ArgoCD reconciles k3s
|
||||
```
|
||||
|
||||
ArgoCD is already running in the homelab k3s cluster, so it is the reconciler (not Flux). GitHub
|
||||
Actions should not receive kubeconfig or direct access to the homelab cluster.
|
||||
|
||||
Manual `kubectl` use should be limited to bootstrapping the ArgoCD Application, read-only
|
||||
inspection, and emergency operations. If you intentionally choose to apply the current manifests
|
||||
by hand during a controlled bootstrap test, the command would be:
|
||||
|
||||
```bash
|
||||
kubectl apply -k k8s/
|
||||
```
|
||||
|
||||
Do not treat that as the normal production deployment process.
|
||||
|
||||
The manifests assume Traefik ingress is available. Read-only research confirmed Traefik and
|
||||
cert-manager are installed, with a ready `letsencrypt-prod` ClusterIssuer. However, TLS and ingress
|
||||
hardening should remain documented rather than applied until the Pangolin/Newt edge path is
|
||||
confirmed.
|
||||
|
||||
Items to confirm before production ingress changes:
|
||||
|
||||
- Whether Pangolin/Newt terminates TLS, passes TLS through, or proxies HTTPS to Traefik.
|
||||
- Whether Pangolin/Newt preserves `Host` headers for the BlueCap hostnames.
|
||||
- Whether cert-manager's Cloudflare DNS-01 token can issue certificates for `bluecapstrategies.com`.
|
||||
- Whether the canonical production host should be apex or `www`.
|
||||
|
||||
## DNS Cutover
|
||||
|
||||
Before cutover:
|
||||
|
||||
- Build and push the final container image.
|
||||
- Confirm the GitHub Actions workflow publishes the GHCR image and updates the desired image tag.
|
||||
- Confirm ArgoCD reconciles the `bluecap-strategies` workload from Git.
|
||||
- Confirm the ingress or edge route answers for `www.bluecapstrategies.com` and `bluecapstrategies.com`.
|
||||
- Lower DNS TTL on the existing records.
|
||||
- Point BlueCap DNS to the existing Pangolin/Newt public edge, or to the chosen production edge if
|
||||
that architecture changes.
|
||||
- Configure apex-to-canonical redirect at the selected layer: Cloudflare, Pangolin/Newt, Traefik,
|
||||
or nginx.
|
||||
- Verify `/`, `/services/`, `/about-us/`, `/contact/`, `/sitemap-index.xml`, and `/robots.txt`
|
||||
after DNS propagation.
|
||||
@@ -0,0 +1,193 @@
|
||||
# BlueCap Strategies GitOps Deployment Strategy (ArgoCD)
|
||||
|
||||
## Goal
|
||||
|
||||
Deploy the BlueCap Strategies Astro site to the existing k3s cluster using GitOps, without
|
||||
giving GitHub Actions direct access to the homelab cluster.
|
||||
|
||||
The homelab already runs **ArgoCD** in k3s, so this strategy uses ArgoCD as the reconciler
|
||||
rather than standing up a second GitOps controller (Flux). ArgoCD pulls from Git and GHCR; the
|
||||
cluster is never exposed to GitHub.
|
||||
|
||||
Desired operating model:
|
||||
|
||||
1. Edit the site locally in this repository.
|
||||
2. Commit changes.
|
||||
3. Push to `main`.
|
||||
4. GitHub Actions runs tests and builds a container image.
|
||||
5. GitHub Actions pushes the image to GHCR, tagged with the immutable `<git-sha>`.
|
||||
6. Git is updated with the new desired image tag (CI commits the tag into `k8s/`).
|
||||
7. ArgoCD, running inside k3s, detects the Git change.
|
||||
8. ArgoCD syncs the Kubernetes manifests (Kustomize).
|
||||
9. k3s pulls the new image and rolls the website deployment.
|
||||
10. Traefik serves the updated site publicly.
|
||||
|
||||
GitHub never receives kubeconfig or direct network access to the homelab cluster. The cluster
|
||||
pulls from Git and the image registry.
|
||||
|
||||
## Prerequisites (not yet done)
|
||||
|
||||
- **This repository is not yet a git repo.** GitOps requires a remote (GitHub) that both CI and
|
||||
ArgoCD can read. `git init`, push to a GitHub repo, then wire ArgoCD to that repo.
|
||||
- Decide the GHCR image path (replace the placeholder `ghcr.io/your-org/...` in
|
||||
`k8s/deployment.yaml`).
|
||||
|
||||
## Cluster Findings
|
||||
|
||||
Read-only `kubectl` inspection previously showed:
|
||||
- k3s API reachable at `10.66.15.30:6443`.
|
||||
- Ingress controller: `traefik`. Ingress class: `traefik`.
|
||||
- cert-manager installed with a `letsencrypt-prod` ClusterIssuer.
|
||||
- Existing namespaces for media, nextcloud, monitoring, etc.
|
||||
- No `bluecap-strategies` namespace yet.
|
||||
- **ArgoCD is already installed and managing other workloads in the cluster.**
|
||||
|
||||
Edge / DNS / TLS (research 2026-06-05):
|
||||
- Edge routing uses a Pangolin/Newt path (`dav.rodgersweb.com` via Caddy).
|
||||
- `bluecapstrategies.com` DNS is managed by Cloudflare.
|
||||
- TLS handled by cert-manager via a Cloudflare DNS-01 token.
|
||||
|
||||
## Repository & Image Strategy
|
||||
|
||||
- **Mono-repo:** Keep site source and k8s manifests in the same repository.
|
||||
- **GHCR:** Use GitHub Container Registry for images.
|
||||
- **Immutable tags:** Deploy via `<git-sha>` tags, never `latest`. `latest` defeats ArgoCD's
|
||||
ability to detect and record what is actually running.
|
||||
- **Manifests:** Plain Kustomize under `k8s/` (already present). ArgoCD consumes
|
||||
`k8s/kustomization.yaml` natively — no Helm chart required.
|
||||
|
||||
## Phase 1: ArgoCD Application (Foundational GitOps)
|
||||
|
||||
Register the site as an ArgoCD `Application`. Two common patterns; pick one to match how the
|
||||
homelab already organizes ArgoCD:
|
||||
|
||||
- **Direct Application** — a single `Application` CR pointing at this repo's `k8s/` path.
|
||||
- **App-of-apps** — add this `Application` to the existing root/app-of-apps repo if the homelab
|
||||
uses that pattern.
|
||||
|
||||
Example direct Application (store in the homelab ArgoCD config repo, or apply once to bootstrap):
|
||||
|
||||
```yaml
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: bluecap-website
|
||||
namespace: argocd
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: https://github.com/<owner>/bcs-website.git
|
||||
targetRevision: main
|
||||
path: k8s
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: bluecap-strategies
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
```
|
||||
|
||||
`prune: true` removes resources deleted from Git; `selfHeal: true` reverts manual drift;
|
||||
`CreateNamespace=true` lets ArgoCD create `bluecap-strategies` (or keep `namespace.yaml` in
|
||||
`k8s/` — both work, don't do both destructively).
|
||||
|
||||
## Image Update Strategy
|
||||
|
||||
Two options — this strategy recommends **Option A** for simplicity and auditability:
|
||||
|
||||
### Option A (recommended): CI commits the image tag
|
||||
GitHub Actions, after pushing the image, patches the image tag in `k8s/` (via
|
||||
`kustomize edit set image` or a `newTag` field) and commits back to `main`. ArgoCD auto-syncs the
|
||||
new tag. Every deploy is a Git commit traceable to a source SHA — clean rollback via `git revert`.
|
||||
|
||||
Add to `k8s/kustomization.yaml`:
|
||||
|
||||
```yaml
|
||||
images:
|
||||
- name: ghcr.io/<owner>/bluecap-strategies-website
|
||||
newTag: <ci-sets-this>
|
||||
```
|
||||
|
||||
### Option B: ArgoCD Image Updater
|
||||
Install ArgoCD Image Updater and annotate the Application to watch the GHCR repo and write back
|
||||
the tag. Removes the CI commit-back step but adds a controller and registry-credential wiring.
|
||||
Only adopt if the homelab already runs Image Updater for other apps.
|
||||
|
||||
## Phase 2: CI Pipeline (GitHub Actions)
|
||||
|
||||
Workflow on push to `main`:
|
||||
|
||||
1. **Quality gate:** `npm ci`, `npm run typecheck`, `npm run build`.
|
||||
2. **E2E validation:** `npm run test:e2e` (Playwright) where browser deps are available.
|
||||
3. **Publish:** build the Docker image, push to GHCR tagged `<git-sha>`.
|
||||
4. **GitOps sync (Option A):** `kustomize edit set image ...=ghcr.io/<owner>/...:<git-sha>`,
|
||||
commit and push to `main`. ArgoCD reconciles.
|
||||
|
||||
CI never touches the cluster — it only writes to GHCR and Git.
|
||||
|
||||
## Phase 3: Staging (Launch Readiness)
|
||||
|
||||
Avoid testing in production. Add a `k8s/overlays/staging/` Kustomize overlay and a second ArgoCD
|
||||
Application tracking it.
|
||||
- **Target:** `staging.bluecapstrategies.com`
|
||||
- **Goal:** let the owner review content/insights changes before they hit the live site.
|
||||
|
||||
## Phase 4: Elite Authority Infrastructure (9/10 Standard)
|
||||
|
||||
### 1. Secret hygiene — Git-managed credentials
|
||||
Avoid manual `kubectl create secret`. Use **Sealed Secrets** or **External Secrets Operator** so
|
||||
GHCR pull tokens and contact-form keys are encrypted in Git and the site is reproducible from
|
||||
source alone. Match whichever the homelab ArgoCD stack already standardizes on.
|
||||
|
||||
### 2. Edge authority — Cloudflare hardening
|
||||
- **WAF & protection:** enable Cloudflare WAF to block automated probes and DDoS.
|
||||
- **Edge caching:** "Cache Everything" for static Astro assets so most traffic serves from
|
||||
Cloudflare's edge regardless of homelab bandwidth.
|
||||
- **Cloudflare Tunnels (optional):** consider `cloudflared` to remove open router ports.
|
||||
|
||||
### 3. Uptime intelligence (observability) — reuse the `monylog` stack
|
||||
The homelab already runs a full LGTM + Alertmanager stack on the `monylog` box (`10.66.15.21`,
|
||||
Ansible role `pglta`), external to k8s. **Do not deploy Uptime Kuma.** Alloy already remote-writes
|
||||
BlueCap pod/deployment metrics (kube-state-metrics) and ships nginx pod logs to Loki. To finish:
|
||||
- **Add `blackbox_exporter`** to the monylog compose + a Prometheus scrape job probing the
|
||||
in-cluster Service and the public URL (uptime, HTTP status, TLS-expiry, latency).
|
||||
- **Add a `web-bluecap.yml` alert rules file** (SiteDown, CertExpiringSoon <14d, High5xx,
|
||||
DeploymentDegraded, PodRestarting) mirroring the existing host-infra rules.
|
||||
- **Alerting** already flows to **Pushover** (OpenBao creds) with ntfy fallback → phone; add a
|
||||
`service: bluecap` label. **Grafana** already present for dashboards. ArgoCD sync/health is an
|
||||
extra deploy-level signal. See `docs/seo-aeo-optimization.md` §6a for detail.
|
||||
|
||||
### 4. Digital ESG — verified sustainability
|
||||
- Verify Green Web status if the homelab uses renewable energy.
|
||||
- Use Astro's `Image` component to minimize data transfer.
|
||||
- Document the low-energy architecture in `llms.txt` or a footer "Digital ESG" note.
|
||||
|
||||
### 5. Security performance (A+ rating)
|
||||
- Harden `deploy/nginx.conf` toward an A+ on `securityheaders.com`: robust CSP, HSTS,
|
||||
X-Frame-Options.
|
||||
- TLS 1.3 only; cert-manager keeps certs well ahead of expiry.
|
||||
|
||||
## Verification & Rollback
|
||||
|
||||
### Rollback
|
||||
GitOps makes rollbacks surgical:
|
||||
1. `git revert <commit-sha>`
|
||||
2. `git push origin main`
|
||||
3. ArgoCD reconciles the previous known-good state automatically. (Or use ArgoCD's
|
||||
History/Rollback UI to pin an earlier synced revision.)
|
||||
|
||||
### Automated checks
|
||||
- `npm run test:e2e` (pre-deployment, in CI)
|
||||
- K8s readiness/liveness probes on `/healthz` (during rollout)
|
||||
- ArgoCD health status + Uptime Kuma (post-deployment)
|
||||
|
||||
## Open Items Before Production Ingress
|
||||
|
||||
- Confirm whether Pangolin/Newt terminates, passes through, or proxies TLS to Traefik, and
|
||||
whether it preserves `Host` headers for the BlueCap hostnames.
|
||||
- Confirm cert-manager's Cloudflare DNS-01 token can issue for `bluecapstrategies.com`.
|
||||
- Decide canonical host (apex vs `www`) and where the apex→canonical redirect lives (Cloudflare,
|
||||
Pangolin/Newt, Traefik, or nginx).
|
||||
@@ -0,0 +1,237 @@
|
||||
# SEO, AEO & AI-Crawler Optimization Plan
|
||||
|
||||
Requirements and rules for optimizing the BlueCap Strategies rebuild for classic search (SEO),
|
||||
answer engines / AI citation (AEO/GEO), and AI-crawler acceptance. Written **2026-07-15** against
|
||||
mid-2026 best practices (see Sources). Where a rule can be machine-enforced, it is defined as a
|
||||
**Zod schema constraint** or a **build-time check** so the build fails when copy violates it —
|
||||
this is the TypeScript/Astro equivalent of Pydantic validation (do **not** add Python/Pydantic to
|
||||
a JS build; Astro Content Collections already run on Zod).
|
||||
|
||||
## 0. Guiding principles (2026 reality check)
|
||||
|
||||
1. **HTML is the source of truth, not `llms.txt`.** AI search crawlers (GPTBot, ClaudeBot,
|
||||
PerplexityBot, OAI-SearchBot, Google-Extended) overwhelmingly **skip `/llms.txt` and crawl
|
||||
rendered HTML directly**; Google confirmed it does not support llms.txt. Keep our `llms.txt`
|
||||
(low cost, ~10% of sites have one) but invest first in semantic HTML, structured data, and
|
||||
crawlable copy. Our Astro output is **fully static** — 100% of meaningful copy is in initial
|
||||
HTML, which is the single biggest AEO advantage over the old JS-heavy WordPress/Elementor site.
|
||||
2. **Structured data drives citation.** Schema lets answer engines verify *who we are*, *what a
|
||||
page covers*, and *which facts are reliable to cite*. FAQ schema has among the highest AI
|
||||
citation rates (question/answer mirrors how engines present answers).
|
||||
3. **Answer-ready structure wins.** Clear headings, direct definitional first sentences, and
|
||||
self-contained paragraphs get extracted; buried or paraphrased marketing prose does not.
|
||||
4. **E-E-A-T via the principal.** Boutique-advisory credibility is entity-driven — a real
|
||||
`Person` entity (Brad Rodgers) with `knowsAbout`, linked to the `Organization`, is a core GEO
|
||||
asset. (Gated on owner-approved bio — see parity plan Phase 5.)
|
||||
|
||||
## 1. Programmatic enforcement (Zod + build linter)
|
||||
|
||||
### 1a. Content-collection SEO schema (fails the build)
|
||||
|
||||
Extend `src/content.config.ts`. Every collection (pages, services, focusAreas, insights) shares
|
||||
an `seo` shape with enforced limits:
|
||||
|
||||
```ts
|
||||
import { z } from "astro:content";
|
||||
|
||||
export const seo = z.object({
|
||||
// <title> renders as "`title` | BlueCap Strategies" — keep the full SERP title ≤ ~60 chars
|
||||
title: z.string().min(10).max(55),
|
||||
// meta description + OG description; 120–158 is the safe SERP/AI-snippet window
|
||||
description: z.string().min(120).max(158),
|
||||
// canonical is derived from slug, but allow override for cross-posted insights
|
||||
canonicalUrl: z.string().url().optional(),
|
||||
// every page must ship a social share image (absolute path under /assets)
|
||||
ogImage: z.string().startsWith("/assets/").default("/assets/og/default.jpg"),
|
||||
// primary entity/topic — powers internal linking + schema keywords
|
||||
primaryTopic: z.string(),
|
||||
noindex: z.boolean().default(false),
|
||||
});
|
||||
```
|
||||
|
||||
Rules encoded here (build fails on violation): title length, description length window, OG image
|
||||
present, canonical is a valid URL. This is the "rules are being followed" guarantee you asked
|
||||
about — enforced on every `npm run build` in CI before ArgoCD ever sees the image.
|
||||
|
||||
### 1b. Build-time SEO linter (what Zod can't see)
|
||||
|
||||
Zod validates frontmatter, not rendered HTML. Add a small check (a Node script run in CI, and/or
|
||||
Playwright assertions in `tests/`) that fails the build if any built page in `dist/` violates:
|
||||
|
||||
- Exactly **one `<h1>`** per page.
|
||||
- Heading hierarchy has no skipped levels (no `<h4>` before an `<h2>`).
|
||||
- `<title>`, `<meta name="description">`, `<link rel="canonical">` all present and non-empty.
|
||||
- Every `<img>` has non-empty `alt` (parity plan already requires this).
|
||||
- At least one JSON-LD block present, and **every** JSON-LD block `JSON.parse`s.
|
||||
- Internal links resolve (no 404s to our own routes).
|
||||
- Canonical host matches the chosen production host (apex vs `www` — see §5).
|
||||
|
||||
Recommended: a `scripts/seo-lint.mjs` that walks `dist/**/*.html` with a lightweight parser, plus
|
||||
keep the existing Playwright suite for link/CTA coverage.
|
||||
|
||||
## 2. Per-page technical SEO requirements
|
||||
|
||||
Already implemented in `src/layouts/BaseLayout.astro` (keep + enforce):
|
||||
|
||||
- `<title>`, `<meta name="description">`, `<link rel="canonical">`
|
||||
- Open Graph (`og:type/title/description/url/image`) + Twitter `summary_large_image`
|
||||
- JSON-LD injection slot (Organization + WebSite globally, page schema per route)
|
||||
- `lang="en"`, viewport, skip-link, RSS `<link rel="alternate">`
|
||||
|
||||
To add/verify:
|
||||
|
||||
- [ ] **Per-page OG images** — currently every page falls back to `/assets/water-strategy.jpg`.
|
||||
Generate topic-specific OG images (services, focus areas, insights) at 1200×630.
|
||||
- [ ] **`robots` meta** wired to the `noindex` frontmatter flag (for `/thank-you/`, staging).
|
||||
- [ ] **`article:published_time` / `modified_time`** OG tags on insights posts.
|
||||
- [ ] **Breadcrumbs** (visible + `BreadcrumbList` schema) on services/focus/insights detail pages.
|
||||
- [ ] **Sitemap** already via `@astrojs/sitemap` (excludes `/thank-you/`); add `lastmod` from
|
||||
content dates for insights.
|
||||
- [ ] **Trailing-slash consistency** — config uses `trailingSlash: "always"`; ensure canonical +
|
||||
internal links all match to avoid duplicate-URL dilution.
|
||||
|
||||
## 3. Structured data (JSON-LD) matrix
|
||||
|
||||
| Page / type | Required schema | Notes |
|
||||
|-------------|-----------------|-------|
|
||||
| Global | `Organization`, `WebSite` | Already in `src/utils/schema.ts`. Add `sameAs` (real socials), `logo`, `contactPoint` (phone/email), `areaServed`. |
|
||||
| Homepage | `Organization` + `WebSite` (+ optional `SearchAction`) | — |
|
||||
| `/services/` + each service | `Service` (with `provider` → Organization, `serviceType`, `areaServed`) | One per detail page. |
|
||||
| `/focus-areas/` + each focus | `Service` or `DefinedTerm`/`Article` | Focus areas double as topic-authority hubs. |
|
||||
| About / Brad Rodgers | `Person` + `AboutPage`; `Person.worksFor` → Organization; `knowsAbout[]` | **Owner-approved facts only.** Core GEO/E-E-A-T asset. |
|
||||
| Insights posts | `Article`/`BlogPosting` (author=Person, publisher=Organization, dates, `headline`, `image`) | Powers AI citation of thought leadership. |
|
||||
| Insights index | `Blog` / `CollectionPage` | — |
|
||||
| Contact | `ContactPage` + `Organization.contactPoint` | Use real values, never the WP placeholders. |
|
||||
| FAQ sections | `FAQPage` — **only where the same Q&A is visibly rendered** | Highest AI-citation rate. Never add invisible FAQ schema. |
|
||||
| Breadcrumbs | `BreadcrumbList` | Detail pages. |
|
||||
|
||||
Rule: **structured data must reflect content that is visibly present on the page.** No hidden-text
|
||||
schema, no unsupported claims, no fabricated review/rating markup.
|
||||
|
||||
## 4. AEO / GEO content rules
|
||||
|
||||
Applied during Phase 2 copy restoration and Phase 5+ enhancements (must not violate the
|
||||
no-paraphrasing parity rule on restored copy):
|
||||
|
||||
- **Answer-first**: each section's opening sentence should stand alone as a direct answer /
|
||||
definition (e.g., "Marine conservation finance is …"). Extractable by answer engines.
|
||||
- **Semantic headings** phrased as the questions/topics people ask ("What makes PFAS remediation a
|
||||
capital formation problem?").
|
||||
- **FAQ modules** on high-intent authority pages (PFAS economics, marine conservation finance) —
|
||||
visible Q&A + `FAQPage` schema. Owner-approved answers only.
|
||||
- **Entity consistency**: use canonical terminology everywhere — *catalytic capital*, *aquatic
|
||||
resources*, *marine & freshwater ecosystems*, *PFAS remediation*, *marine conservation finance*,
|
||||
*environmental markets*, *coastal resilience*. Feeds `primaryTopic` + internal linking.
|
||||
- **Internal linking graph**: services ↔ focus areas ↔ insights ↔ principal bio. Dense, relevant
|
||||
internal links are a top GEO signal.
|
||||
- **Freshness**: insights carry `publishDate`/`modifiedDate`; surface "Updated" dates.
|
||||
- **Statistics & specifics**: the homepage metrics (27 years, $10B+, $3.5B+, 275+) are exactly the
|
||||
kind of concrete, citable facts answer engines favor — keep them prominent and, where true,
|
||||
attributable.
|
||||
|
||||
## 5. AI-crawler policy (`robots.txt`) — **owner decision required**
|
||||
|
||||
Current `public/robots.txt` = `Allow: /` for everyone. 2026 best practice is **explicit per-agent
|
||||
rules**, splitting *search/answer* bots (that can cite and send traffic) from *training* bots
|
||||
(that ingest content into model weights). Decision matrix:
|
||||
|
||||
| Bot | Job | Recommendation |
|
||||
|-----|-----|----------------|
|
||||
| `OAI-SearchBot`, `ChatGPT-User` | ChatGPT search index + live citation fetch | **Allow** |
|
||||
| `Claude-SearchBot` | Claude web-search citations | **Allow** |
|
||||
| `PerplexityBot` | Perplexity answer citations | **Allow** |
|
||||
| `Google-Extended` | Gemini + Google AI Overviews | **Allow** (maximizes AI visibility) |
|
||||
| `Googlebot`, `Bingbot` | Classic search | **Allow** |
|
||||
| `GPTBot` | OpenAI model *training* | Owner choice — allow = max reach, disallow = opt out of training |
|
||||
| `ClaudeBot` | Anthropic model *training* | Owner choice (same trade-off) |
|
||||
| `Bytespider`, stealth scrapers | Known to ignore robots.txt | Consider **edge/WAF block** (Cloudflare) — robots.txt won't stop them |
|
||||
|
||||
**Recommended default for an authority-seeking advisory firm: allow all search/answer + classic
|
||||
bots (maximize citation and discoverability); training bots are a values call.** Because BlueCap
|
||||
wants to be cited as an authority, allowing everything is the simplest high-visibility posture.
|
||||
Non-compliant scrapers are handled at the Cloudflare edge (see gitops doc §4.2), not robots.txt.
|
||||
|
||||
Keep `llms.txt` (already present, low cost) but treat it as supplemental, not load-bearing.
|
||||
|
||||
## 6. Measurement & Monitoring — **recommendation, needs sign-off**
|
||||
|
||||
Three distinct layers; today none is decided.
|
||||
|
||||
### 6a. Uptime / infrastructure — reuse the existing `monylog` stack (do NOT deploy Uptime Kuma)
|
||||
|
||||
The homelab already runs a full self-hosted **LGTM + Alertmanager** stack on the `monylog` box
|
||||
(`10.66.15.21`, Ansible role `pglta`): Prometheus (15d retention, remote-write receiver), Grafana,
|
||||
Loki, Tempo, Alertmanager → **Pushover** (creds from OpenBao) with **ntfy** fallback → phone. It
|
||||
runs **outside k8s on purpose** so it still alerts when the cluster is down. Grafana Alloy runs as
|
||||
a k3s DaemonSet and remote-writes cluster metrics + ships all pod logs to monylog.
|
||||
|
||||
**Already flowing for BlueCap (zero work):**
|
||||
- **Pod/deployment health** — Alloy scrapes kube-state-metrics + annotated pods → Prometheus.
|
||||
`kube_deployment_status_replicas_unavailable`, restarts, OOMKills are queryable now.
|
||||
- **nginx logs** — Alloy ships all pod logs to Loki. 5xx rate / errors available via LogQL now.
|
||||
|
||||
**To add (small):**
|
||||
1. **blackbox_exporter** — the one gap (not in the monylog compose). Add as a compose service +
|
||||
a Prometheus scrape job. Probe the in-cluster Service (app health) **and** the public URL (full
|
||||
edge-path uptime + TLS expiry). monylog is off-cluster → probing the public URL is a genuine
|
||||
external vantage. Metrics: `probe_success`, `probe_http_status_code`,
|
||||
`probe_ssl_earliest_cert_expiry`, latency.
|
||||
2. **`web-bluecap.yml` alert rules** (mirror the existing host-infra rules pattern): `SiteDown`
|
||||
(`probe_success==0` for 2m), `CertExpiringSoon` (<14d), `High5xx` (Loki/blackbox),
|
||||
`DeploymentDegraded`, `PodRestarting`.
|
||||
3. **Alertmanager** already routes to Pushover — just add a `service: bluecap` label; no new
|
||||
channel. **Grafana** already present → build a BlueCap uptime/latency/5xx/pod-health dashboard.
|
||||
- **ArgoCD** health/sync status as an additional deploy-level signal.
|
||||
|
||||
### 6b. Visitor & behavior analytics — PostHog + Umami side-by-side trial (ADR-0005)
|
||||
|
||||
Decided: **run PostHog and Umami side-by-side initially** and keep whichever fits best. This is
|
||||
**product/visitor analytics**, distinct from the `monylog` ops telemetry in §6a. Trial setup:
|
||||
|
||||
- **PostHog — Cloud free tier** (1M events/mo) for the trial, not self-host (self-hosting needs
|
||||
ClickHouse/Kafka/etc.; PostHog's docs steer small users to Cloud). Reverse-proxy ingest through
|
||||
our domain to reduce ad-blocker loss. Gives autocapture, funnels, session replay.
|
||||
- **Umami — self-hosted** in k3s: cookieless, ~2 KB, no consent banner, always-on baseline
|
||||
(pageviews, sources, devices, custom events). Aligns with the Digital-ESG positioning.
|
||||
- Both wired through one config-driven `Analytics.astro` in `BaseLayout`, gated by env vars, so
|
||||
either can be toggled and the winner kept without code churn. Same event set in both:
|
||||
consultation-CTA clicks, per-service/focus CTA source (ties to the segmented-CTA strategy),
|
||||
insights reads, contact submits.
|
||||
- **Consent/privacy must cover the superset** — PostHog (esp. session replay) likely triggers
|
||||
cookie-consent + privacy-policy obligations; add these **before** enabling PostHog in production.
|
||||
Umami alone needs no banner.
|
||||
- **Avoid GA4** unless Google Ads attribution is later required.
|
||||
- Evaluate on insight value vs. weight/consent/ESG fit; record the winner in a follow-up ADR.
|
||||
|
||||
### 6c. Search & AI-citation visibility
|
||||
- **Google Search Console** + **Bing Webmaster Tools** (free, essential) — submit sitemap, monitor
|
||||
impressions/queries/coverage, Core Web Vitals.
|
||||
- **AI-citation tracking** (emerging, optional): periodically test brand/topic prompts across
|
||||
ChatGPT/Perplexity/Google AI Overviews and log whether BlueCap is cited. Lightweight manual
|
||||
tracker to start; dedicated GEO tools are immature — don't over-invest yet.
|
||||
- **Performance budget**: Lighthouse in CI; target 95+ SEO/Perf/Best-Practices/Accessibility.
|
||||
Static Astro should hit this easily; guard against regressions from images (use Astro `Image`).
|
||||
|
||||
## 7. Pre-launch SEO checklist
|
||||
|
||||
- [ ] Zod SEO schema in place; build fails on title/description/OG/canonical violations.
|
||||
- [ ] `scripts/seo-lint.mjs` (single H1, heading order, canonical, alt text, JSON-LD parses) green in CI.
|
||||
- [ ] Every page: unique title, description in window, canonical, OG image, valid JSON-LD.
|
||||
- [ ] Organization/WebSite/Service/Person/Article/Breadcrumb schema per §3, all visible-content-backed.
|
||||
- [ ] `robots.txt` per §5 (owner-decided); `sitemap-index.xml` correct; `/thank-you/` + staging `noindex`.
|
||||
- [ ] Canonical host decided (apex vs `www`) + redirect configured at the edge.
|
||||
- [ ] Search Console + Bing verified, sitemap submitted.
|
||||
- [ ] Analytics deployed (Umami) + consultation/CTA events firing.
|
||||
- [ ] Uptime Kuma + external check live and alerting.
|
||||
- [ ] Lighthouse ≥ 95 across the board; no CLS from unsized images.
|
||||
|
||||
## Sources (verified 2026-07-15)
|
||||
|
||||
- [The State of llms.txt in 2026 — aeo.press](https://www.aeo.press/ai/the-state-of-llms-txt-in-2026)
|
||||
- [llms.txt Explained (May 2026) — Codersera](https://codersera.com/blog/llms-txt-complete-guide-2026/)
|
||||
- [Structured Data for AEO and GEO: Schema Markup Guide 2026 — Kurieta](https://kurieta.com/schema-for-aeo-geo/)
|
||||
- [Answer Engine Optimization: Complete AEO Guide [2026] — Frase](https://www.frase.io/blog/what-is-answer-engine-optimization-the-complete-guide-to-getting-cited-by-ai)
|
||||
- [Are FAQ Schemas Important for AI Search, GEO & AEO? — Frase](https://www.frase.io/blog/faq-schema-ai-search-geo-aeo)
|
||||
- [The AI User-Agent Landscape in 2026: A Complete Reference — No Hacks](https://nohacks.co/blog/ai-user-agents-landscape-2026)
|
||||
- [AI Crawlers Explained: GPTBot, ClaudeBot, PerplexityBot (2026) — Anagram](https://www.anagram.ai/blog/ai-crawlers-explained-gptbot-claudebot-perplexitybot-and-how-to-let-them-in-2026)
|
||||
- [Robots.txt for AI Crawlers in 2026 — Cubitrek](https://cubitrek.com/blog/robots-txt-2026-managing-ai-crawler-budgets)
|
||||
Reference in new issue
Block a user