diff --git a/src/config.py b/src/config.py
index bb72f70f..c257bb8d 100644
--- a/src/config.py
+++ b/src/config.py
@@ -571,6 +571,11 @@ class Settings(BaseSettings):
content_meilisearch_url: str = "http://localhost:7700"
content_meilisearch_api_key: str = ""
+ # ── SEO / Public Site ──────────────────────────────────────────────────
+ # Canonical base URL used in sitemap.xml, canonical link tags, and OG tags.
+ # Override with SITE_URL env var, e.g. "https://alexanderwhitestone.com".
+ site_url: str = "https://alexanderwhitestone.com"
+
# ── Scripture / Biblical Integration ──────────────────────────────
# Enable the biblical text module.
scripture_enabled: bool = True
diff --git a/src/dashboard/app.py b/src/dashboard/app.py
index 68503cdf..b4f29315 100644
--- a/src/dashboard/app.py
+++ b/src/dashboard/app.py
@@ -63,6 +63,7 @@ from dashboard.routes.tools import router as tools_router
from dashboard.routes.tower import router as tower_router
from dashboard.routes.voice import router as voice_router
from dashboard.routes.work_orders import router as work_orders_router
+from dashboard.routes.seo import router as seo_router
from dashboard.routes.world import matrix_router
from dashboard.routes.world import router as world_router
from timmy.workshop_state import PRESENCE_FILE
@@ -664,6 +665,7 @@ if static_dir.exists():
from dashboard.templating import templates # noqa: E402
# Include routers
+app.include_router(seo_router)
app.include_router(health_router)
app.include_router(agents_router)
app.include_router(voice_router)
diff --git a/src/dashboard/routes/seo.py b/src/dashboard/routes/seo.py
new file mode 100644
index 00000000..b943870f
--- /dev/null
+++ b/src/dashboard/routes/seo.py
@@ -0,0 +1,73 @@
+"""SEO endpoints: robots.txt, sitemap.xml, and structured-data helpers.
+
+These endpoints make alexanderwhitestone.com crawlable by search engines.
+All pages listed in the sitemap are server-rendered HTML (not SPA-only).
+"""
+
+from __future__ import annotations
+
+from datetime import date
+
+from fastapi import APIRouter
+from fastapi.responses import PlainTextResponse, Response
+
+from config import settings
+
+router = APIRouter(tags=["seo"])
+
+# Public-facing pages included in the sitemap.
+# Format: (path, change_freq, priority)
+_SITEMAP_PAGES: list[tuple[str, str, str]] = [
+ ("/", "daily", "1.0"),
+ ("/briefing", "daily", "0.9"),
+ ("/tasks", "daily", "0.8"),
+ ("/calm", "weekly", "0.7"),
+ ("/thinking", "weekly", "0.7"),
+ ("/swarm/mission-control", "weekly", "0.7"),
+ ("/monitoring", "weekly", "0.6"),
+ ("/nexus", "weekly", "0.6"),
+ ("/spark/ui", "weekly", "0.6"),
+ ("/memory", "weekly", "0.6"),
+ ("/marketplace/ui", "weekly", "0.8"),
+ ("/models", "weekly", "0.5"),
+ ("/tools", "weekly", "0.5"),
+ ("/scorecards", "weekly", "0.6"),
+]
+
+
+@router.get("/robots.txt", response_class=PlainTextResponse)
+async def robots_txt() -> str:
+ """Allow all search engines; point to sitemap."""
+ base = settings.site_url.rstrip("/")
+ return (
+ "User-agent: *\n"
+ "Allow: /\n"
+ "\n"
+ f"Sitemap: {base}/sitemap.xml\n"
+ )
+
+
+@router.get("/sitemap.xml")
+async def sitemap_xml() -> Response:
+ """Generate XML sitemap for all crawlable pages."""
+ base = settings.site_url.rstrip("/")
+ today = date.today().isoformat()
+
+ url_entries: list[str] = []
+ for path, changefreq, priority in _SITEMAP_PAGES:
+ url_entries.append(
+ f"