# Technical SEO Audit — Quire Guide (quire.io/guide/)

Audited: https://quire.io/guide/ and representative subpages (`/guide/add-task/`, `/guide/de/add-task/`, `/guide/zh/`, `/guide/zh_TW/`, `/guide/zh_HK/`) across all 13 locales, plus `robots.txt`, `sitemap.xml` (index) and `guide/sitemap.xml`, and the Jekyll source in this repo (`_layouts/default.html`, `_includes/head.html`, `_data/i18n.yml`).

## Score: 78/100

Site is fundamentally sound: fully server-rendered static HTML, clean crawlable URL structure, complete hreflang reciprocity across 14 locale variants, HSTS + gzip, valid sitemap, and FAQPage structured data on articles. The main deductions come from an invalid-syntax hreflang bug affecting 3 of 13 locales, a missing x-default, duplicate canonical/viewport tags from a template bug, a zoom-blocking viewport, and some CWV/CLS risk from undimensioned images.

---

## 1. Crawlability — PASS

- `robots.txt` (200 OK) allows `/guide/` fully; only `/api`, `/oauth`, `/qa`-`/qs`, `/r/*` admin/app paths are disallowed, none of which touch guide content.
- Explicit `User-agent: ClaudeBot` and `Bytespider` blocks exist (with `Crawl-delay`), showing the site already manages AI-crawler access — but they still `Allow: /`, so guide content remains fully crawlable to AI bots.
- `Sitemap: https://quire.io/sitemap.xml` is declared in robots.txt.
- Sitemap index (`https://quire.io/sitemap.xml`) correctly references `https://quire.io/guide/sitemap.xml` as a dedicated child sitemap.
- No `noindex` meta robots tags found on sampled pages (the `<meta name="robots">` tag is conditionally emitted only when `page.robots` front matter is set — good, opt-in pattern, low risk of accidental blanket noindex).
- Custom 404 page returns a real `404` HTTP status (not a soft-404 200), confirmed on both a nonexistent English slug and a nonexistent translated slug.

## 2. Indexability / Sitemap — PASS with issues

- `guide/sitemap.xml`: 1932 URLs, all well-formed, all HTTPS, all with consistent trailing slashes, no malformed loc entries.
- Exact 1:1 translation parity confirmed: 138 URLs for English root + 138 URLs for **each** of the 13 locale directories (de, es, fr, it, ja, ko, pt, pt_BR, ru, tr, zh, zh_HK, zh_TW) = 1932 total, with zero orphaned/missing pages in any language.
- `lastmod` dates are all sane (most recent 2026-06-30, nothing future-dated relative to the audit date).
- Canonical tags are **self-referential and correct in value** on every sampled page (homepage, English article, German article) — e.g. `/guide/de/add-task/` canonicalizes to itself, not to the English version. This is the right pattern for a translated (not duplicate) content site.
- **Bug**: `<link rel="canonical">` is emitted **twice** on every page, from `_layouts/default.html` lines 59 and 78 (both render the same correct URL, but it's a literal duplicate tag within the same template file — a copy/paste leftover, not a value conflict). Low practical risk since values match, but invalid HTML and fragile if one copy is ever edited without the other.
- No duplicate/thin-content risk found between the three Chinese variants: spot-checked `zh` (Simplified) vs `zh_TW`/`zh_HK` (Traditional) on `/add-task/` — genuinely distinct translations (different wording, not just a script conversion), and `zh_TW` vs `zh_HK` differ in regional phrasing (你/您) as expected for legitimate regional variants.

## 3. Hreflang — HIGH-PRIORITY ISSUES (cross-referencing sitemap specialist's findings)

- Reciprocity is complete: every one of the 1932 pages emits the full 14-way alternate set (13 locales + self), generated from `_data/lang_index` + `_data/i18n.yml`, and every sampled page correctly self-references.
- **Confirmed at the source** (`_data/i18n.yml`): three locale codes are emitted into the `hreflang` attribute using **underscores instead of BCP47 hyphens**: `pt_BR`, `zh_TW`, `zh_HK` (should be `pt-BR`, `zh-TW`, `zh-HK`). This is present on all 1932 pages (3 malformed tags × 1932 pages). Underscore-separated region subtags are invalid per BCP47/RFC 5646, which is what the `hreflang` attribute requires. The fix is trivial and already latent in the codebase: `_data/i18n.yml` already has a correct `bcp47:` field per language (e.g. `bcp47: "pt-BR"`, `bcp47: "zh-TW"`, `bcp47: "zh-HK"`) that is defined but **not used** — `_includes/head.html` line 12 emits `lang.code` (the internal directory-name code) instead of `lang.bcp47` in the `hreflang` attribute. Recommend swapping to `lang.bcp47` for the hreflang value while keeping `lang.code` for URL path construction.
- **Missing `hreflang="x-default"`** on all 1932 pages — no fallback annotation for languages/regions not in the explicit list. Recommend adding one `x-default` alternate per page, pointing at the English URL.
- The optional `?lang=auto` redirect script (`assets/js/lang-auto.js`) is a good pattern from a cloaking/SEO-safety standpoint: it only fires when a `lang=auto` query param is explicitly present (e.g., from an outbound marketing link), not automatically on every visit based on `Accept-Language` or geo-IP — so Googlebot and normal direct navigation are never silently redirected. It also correctly maps BCP47 tags to internal codes via the same `bcp47`-shaped keys that should be used for the hreflang attribute, confirming the fix above is safe and low-risk.

## 4. Security Headers — MEDIUM

Sampled headers (homepage, EN article, DE article):
```
strict-transport-security: max-age=63072000; includeSubdomains; preload   ✓
x-frame-options: SAMEORIGIN                                               ✓
content-encoding: gzip                                                    ✓
```
Missing across all sampled pages:
- `Content-Security-Policy` — absent
- `X-Content-Type-Options: nosniff` — absent
- `Referrer-Policy` — absent
- `Cache-Control` — absent (only `ETag` + `Last-Modified` are present; static guide HTML/CSS/JS would benefit from explicit `max-age`/`stale-while-revalidate` for repeat-visit performance, relevant to CWV)

HTTPS itself is solid: HTTP→HTTPS redirects with a single-hop 301, HSTS includes `preload` and `includeSubdomains`.

## 5. URL Structure & Redirects — PASS

- Clean, human-readable slugs (`/guide/add-task/`, `/guide/de/add-task/`), consistent trailing slashes, no query-string pagination or session params in canonical/sitemap URLs.
- `http://quire.io/guide/` → `https://quire.io/guide/`: single 301 hop, no chain.
- `https://quire.io/guide` (no trailing slash) → `https://quire.io/guide/`: single 301 hop, no chain.
- No redirect chains (>1 hop) observed in any sampled path.

## 6. Mobile-Friendliness — MEDIUM ISSUE

- Viewport meta tag is **duplicated with conflicting values**, both from source template bugs:
  - `_layouts/default.html:74` → `width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no` (blocks pinch-zoom — a mobile-usability/accessibility anti-pattern, fails WCAG 1.4.4 and Lighthouse's "viewport" mobile-friendly check)
  - `_includes/head.html:30` → `width=device-width, initial-scale=1.0` (correct, zoom-friendly)
  - Because the first tag appears earlier in the compiled `<head>`, most browsers will honor the zoom-blocking version. Recommend deleting the duplicate in `_layouts/default.html:74` and keeping only the `head.html` version.
- Responsive `<meta name="viewport">` intent is otherwise correct once de-duplicated; no other mobile-blocking patterns (no fixed-width layout indicators, no separate m-dot domain, no interstitials found).

## 7. Core Web Vitals (source-inspection only, no field/lab data collected) — MEDIUM

- **CLS risk**: sampled article images (`/guide/add-task/`) have **no `width`/`height` attributes** on any of the 8 `<img>` tags found, and no CSS `aspect-ratio` reservation confirmed. Without explicit dimensions, images loading in a text-heavy help article are a classic cause of layout shift as they pop in. Recommend adding explicit `width`/`height` (or CSS `aspect-ratio`) to all guide images.
- **LCP/render-blocking risk**: 4 render-blocking stylesheets are loaded synchronously in `<head>` (`/guide/css/main.css`, `/s/css/bootstrap/bootstrap.min.css`, `/s/css/blog.css`, `/guide/assets/css/main.css` — note two separately-named `main.css` files loading, worth checking for redundancy/overlap). A Google Fonts stylesheet (`fonts.googleapis.com`, Inter family) is also loaded synchronously with no `rel="preconnect"` to `fonts.googleapis.com`/`fonts.gstatic.com`, adding avoidable connection-setup latency before text can render in the chosen font.
- **INP**: nothing alarming from source inspection — page relies on modest vanilla JS (search via lunr.js, nav toggle, footer script) rather than a heavy client framework; no long synchronous inline scripts of concern were found in `<head>`.
- No field data (CrUX) was queried as part of this source-level audit; recommend pulling PageSpeed Insights / CrUX numbers to confirm real-world LCP/INP/CLS against the thresholds above.

## 8. Structured Data — PASS, minor opportunity

- Sitewide `@graph` with `WebSite` + `Organization` schema present in every page's `<head>` (via `_seo.html`), including `sameAs` social profiles, founder, and `ContactPoint` — solid entity/brand signal for AI Overviews and Knowledge Panel eligibility.
- `FAQPage` JSON-LD is present on FAQ-equipped articles (confirmed on `/guide/add-task/`), matching the project's documented FAQ convention (frontmatter `faq:` block feeding the schema).
- `CreativeWork` microdata (`itemprop="headline"`, `datePublished`, `dateModified`) is present inline on the article `<header>`, giving a secondary freshness signal beyond the JSON-LD.
- No `HowTo` schema on step-by-step task pages — noted only for completeness; this is already a tracked/pending decision internally (on hold pending AI-citation data collection) and not treated as a new finding here.

## 9. JavaScript Rendering — PASS

- Confirmed the guide is genuinely server-rendered: full article text, headings, and FAQ content are present in the raw HTML response (no client-side hydration required to see content). `curl` without JS execution returns the complete article body.
- The one caveat: every page's raw HTML includes a large chunk of shared app-shell chrome from the main quire.io product (login/signup buttons, a notifications panel with "Mark all as read/unread," an "outdated browser" upgrade banner) rendered ahead of the actual guide content in the DOM. This is static SSR HTML (not a rendering blocker for crawlers), but it does add boilerplate/noise ahead of the true content, slightly diluting text-to-boilerplate ratio per page. Low priority, but worth being aware of for AI-extraction quality (the first ~300 characters of visible body text on a fetched page are app-chrome, not guide content).
- `assets/js/lang-auto.js` is opt-in only (`?lang=auto` param), not an automatic geo/browser-based redirect — correctly avoids cloaking/redirect risk for crawlers and direct navigation.

## 10. IndexNow Protocol

- No evidence of IndexNow key file or ping integration found in the crawled markup or headers. Given Bing/Yandex/Naver support IndexNow and the site already has a mature sitemap + lastmod pipeline, adding an IndexNow push on publish (reusing the existing "sync translation" build pipeline mentioned in recent commits) would be a low-effort win for faster non-Google indexing. Not scored as a failure since it's not a required baseline, but flagged as an opportunity.

---

## Summary of Fixes by File

| File | Issue | Fix |
|---|---|---|
| `_includes/head.html:12` | hreflang uses `lang.code` (underscore variants invalid) | Use `lang.bcp47` for the `hreflang` attribute value |
| `_includes/head.html:12` (loop) | No `x-default` alternate | Add one `x-default` entry per page pointing to the English URL |
| `_layouts/default.html:59` & `:78` | Duplicate `<link rel="canonical">` | Remove one of the two duplicate lines |
| `_layouts/default.html:74` | Zoom-blocking viewport duplicate | Remove; keep only `_includes/head.html:30`'s viewport |
| Guide image templates | No `width`/`height` on `<img>` | Add explicit dimensions or CSS `aspect-ratio` to prevent CLS |
| `_layouts/default.html:82` (Google Fonts) | No preconnect | Add `<link rel="preconnect" href="https://fonts.googleapis.com">` and `https://fonts.gstatic.com` |
| Server/CDN config (nginx) | Missing security/cache headers | Add `X-Content-Type-Options: nosniff`, `Referrer-Policy`, `Content-Security-Policy`, and `Cache-Control` for static assets |
