# The website — gurbanisoul.com
The public website gurbanisoul.com: DNS, SEO, Open Graph images, security headers, imagery policy, screenshots and its gates.
- Page:
- Updated: 2026-10-26
- From the Sri Guru Granth Sahib Ji — Knowledge Base wiki. Sri Guru Granth Sahib Ji is quoted verbatim, cited by Ang, and never altered.
---
The public web property is one Astro static site (`frontend/`) served on the product domain
**`gurbanisoul.com`**. It carries two distinct things, deliberately kept separate (see
[Brand & domains](https://docs.gurbanisoul.com/engineering/brand/)):
- **The Gurbani Soul pages** — `/`, `/features`, `/watch`, `/learn` and its articles, `/privacy`
and `/support` — use the marketing layout (`Marketing.astro`): the product's home, its policy
pages and the Learn articles.
- **The Knowledge Base** — `/library` (its home), `/search`, `/reader`, `/nitnem`, `/browse`, `/themes`, `/lineage`,
`/trail`, `/analytics` (Insights), `/constellation`, `/raag-clock`, `/divergence` — uses
`Base.astro`: the **Sri Guru Granth Sahib Ji Knowledge Base**, the scholarly study site, with
its own name, header and theme.
Both are built by `astro build` into `frontend/dist/`, synced to `webapp/static/`, and served by
the stdlib Python server locally and by Vercel in production. There is no second site and no
second copy of the policy pages — `/privacy` and `/support` are Astro pages under
`frontend/src/pages/`, and they are the single source of truth for the App Store URLs.
---
## Domains & DNS
| Host | Role | Serves |
|---|---|---|
| `gurbanisoul.com` | **canonical** apex | the site (200, no redirect) |
| `www.gurbanisoul.com` | alias | **308** → apex |
| `sggs-knowledge-base.vercel.app` | legacy Vercel alias | **308** → apex |
- **Registrar:** Hostinger (2-year term, auto-renew on, WHOIS privacy on).
- **DNS:** delegated to **Cloudflare** (account label *Company-Domains*), nameservers
`jamie.ns.cloudflare.com` / `luke.ns.cloudflare.com`. The apex/`www` records are
**CNAME → Vercel, DNS-only (grey cloud, not proxied)** — Vercel terminates TLS and issues the
certificate, and HSTS is on. We do **not** proxy through Cloudflare (it would double-terminate
TLS and hide Vercel's own caching/headers).
- **Zone ID / Account ID / API tokens** live in the Cloudflare dashboard only — never in this repo.
**Canonical rule.** The apex is canonical. ``, `og:url` and the sitemap all
use bare `https://gurbanisoul.com/…`. `www` and the old `*.vercel.app` alias 308-redirect to it, so
there is exactly one indexable host. This is asserted at release time by
`sggs-verify-prod` (a no-redirect guard on `/`, `/privacy`, `/support`) and continuously by
`uptime.yml`.
### Email
Mail for `gurbanisoul.com` is hosted by **Zoho Mail** (since 2026-09-26). `support@gurbanisoul.com` is an
alias of the owner's mailbox, and a catch-all delivers any other address at the domain to it. DNS (Cloudflare,
DNS only): MX `mx`/`mx2`/`mx3.zoho.com.au`, SPF `v=spf1 include:zohomail.com.au ~all`, DKIM selector `zmail`,
and DMARC `v=DMARC1; p=none; rua=mailto:support@gurbanisoul.com` on `_dmarc` (move to `p=quarantine` after two
weeks of clean reports). Do not add Cloudflare Email Routing's SPF include: it would break delivery. The address
is the public contact in the App Store listing, the EU Digital Services Act trader declaration (verified
2026-09-26), `SECURITY.md`, and the privacy/support pages. See
[runbook: support-inbox](https://docs.gurbanisoul.com/process/runbooks/support-inbox/).
---
## SEO
`frontend/astro.config.mjs` sets `site: 'https://gurbanisoul.com'`; `frontend/src/site.ts` holds
`SITE_URL`, `SUPPORT_EMAIL`, `APP_STORE_URL`, `APP_STORE_ID`, the `APP_STORE_LIVE` switch, and the
photo-credit list. The head
SEO/social/PWA block is a shared component, **`frontend/src/components/Seo.astro`** (used by both
`Marketing.astro` and the Knowledge Base's `Base.astro`). Every page emits:
- `` and `og:url` built from `SITE_URL` + the path (bare apex host);
- `description`, `og:title/description/type/image/site_name`, `twitter:card=summary_large_image`;
- `theme-color` — always one meta that follows the *page* theme, never the OS. The Knowledge Base
(`themeColor="page"`, the default) gets `id="kbThemeColor"`: the inline boot script in
`Base.astro` sets it to the header's colour for the stored theme before first paint, and
`scripts/core.ts` keeps it equal to `--chrome` afterwards; the light-first marketing shell (`themeColor="light"`) gets
`id="themeColorMeta"`, pinned to paper and rewritten by `scripts/theme.ts` on toggle.
- `apple-mobile-web-app-title`, ``, an `apple-touch-icon`, an SVG favicon, and —
once the app is live (`APP_STORE_LIVE`) — the `apple-itunes-app` Smart App Banner.
`og:image` defaults to the page's OG card (`/og/.png`, see **OG images** below).
`frontend/public/robots.txt` points crawlers at the canonical host and names `/sitemap.xml`, which
is now **generated** (see **Routes, sitemap & RSS**). `Base.astro` derives its canonical/OG from
`Astro.url.pathname` so every Knowledge Base page is self-consistent without per-page props.
The `SeoInvariants` gate checks every built page's canonical/description/`og:image`(exists on disk)/
`theme-color`, and that any `hreflang` alternate is `en`/`x-default` and self-referential (no `/pa/`).
### Routes, sitemap & RSS
- **`frontend/src/routes.ts`** is the single manifest of public routes (`{ path, lastmod,
changefreq, priority, og }`). It must list **exactly** the pages that build to HTML — the
`SitemapInvariants` gate compares the sitemap's `` set to the built HTML route set. `lastmod`
is a **fixed** ISO date (never `Date.now()`) so the sitemap is byte-deterministic.
- **`frontend/src/pages/sitemap.xml.ts`** emits `/sitemap.xml` over the manifest, each URL
self-referencing its `en` + `x-default` hreflang alternates (no `pa` pages ship yet).
- **`frontend/src/pages/rss.xml.ts`** emits `/rss.xml` ("Gurbani Soul — Learn"): every
non-draft Learn article, newest first. `RssInvariants` checks it parses and links to the site.
- The old `frontend/public/sitemap.xml` was deleted (Astro forbids a public file colliding with a
page route).
### OG images
`frontend/src/pages/og/[slug].png.ts` renders each 1200×630 card at build with **satori**
(HTML/CSS → SVG) + **@resvg/resvg-js** (SVG → PNG): warm-ink background, a gold ੴ, the title in
Source Serif 4, the "Gurbani Soul" wordmark, a thin gold rule. `getStaticPaths` returns one card per
distinct `og` slug in `routes.ts` (at least `home` and `knowledge-base`). satori needs **static**
(non-variable) TTFs, so `frontend/src/og/fonts/` holds pinned instances (`*-og.ttf`) of Source
Serif 4 and Sant Lipi with their OFL licences; only the single ੴ glyph is Gurmukhi (satori has no
Indic shaping). Cards are byte-deterministic and each ≤ 150 KB. `public/og.jpg` remains a hero crop
for any fixed-image use.
### Analytics
`frontend/src/components/Analytics.astro` is an `is:inline` loader that injects
`/_vercel/insights/script.js` **only** when `location.hostname` matches `/(^|\.)gurbanisoul\.com$/`
— a true no-op on serve.py, localhost and staging. There is **no** `@vercel/analytics` import and no
third-party host. Vercel Web Analytics must also be **enabled in the Vercel project** (owner action)
for data to be collected; until then the loader simply fetches nothing. The `ExternalRequestAllowlist`
gate proves the marketing HTML/JS only references an approved host set and that the insights loader
is always behind the hostname guard.
### Structured data (JSON-LD)
Emitted through **`frontend/src/components/JsonLd.astro`**, which serialises the object and escapes
`<` so the payload can never break out of its `