Skip to content

SEO: fix OG tags, canonicals, titles & meta descriptions (Ahrefs May 2026 audit) #23

@coderdan

Description

@coderdan

Source: Ahrefs full-site crawl of cipherstash.com, 31 May 2026. This issue covers everything the audit attributed to /docs/*, which lives in this repo. Current-state <head> tags below were verified live on 31 May.

TL;DR

The docs site is a Next.js App Router app. Almost all of the audit's volume comes from /docs/* and is fixable with three template-level changes plus one content/generator pass:

  1. Add og:url + og:type to the metadata template → clears 172 pages.
  2. Add a self-referencing canonical link (currently none exist on any page) → clears the duplicate-pages error + hardens trailing-slash dupes.
  3. Add a global title.template suffix and fix broken section-index titles → clears most of 76 short titles.
  4. Enrich the API-reference generator's title/description templates → clears most of 122 short descriptions.

Verified current <head> on /docs/stack/cipherstash/encryption:

<html lang="en">                            <!-- ✅ lang present -->
<title>/ENCRYPTION</title>                 <!-- ❌ broken title -->
<meta name="description" content="Searchable field-level encryption. ...">
<meta property="og:title" content="/ENCRYPTION">
<meta property="og:description" content="...">
<meta property="og:image" content="https://docs.cipherstash.com/og/stack/cipherstash/encryption/image.png">
<meta name="twitter:card" content="summary_large_image">   <!-- twitter:* all present -->
<!-- ❌ NO og:url -->
<!-- ❌ NO og:type -->
<!-- ❌ NO <link rel="canonical"> -->

Priority summary

# Issue Pages Root cause Fix
1 Open Graph incomplete 172 og:url + og:type never emitted Add both to metadata template
2 Duplicate pages, no canonical 4 No <link rel=canonical> anywhere Self-canonical + trailing-slash policy
3 Title too short / broken 76 No title suffix; /SECTION index bug; generic "Overview"; bare API symbols title.template + fix index titles + enrich API-ref generator
4 Meta description too short 122 Auto-gen "API reference for X" (~25-45 chars) + thin content Enrich API-ref generator + content pass
5 /docs index missing title 1 Root page emits no <title> Add a title
6 Slow page 1 /docs/stack/cipherstash/proxy/encrypt-tool TTFB ~6.4s Profile SSR/data fetch
7 Trailing-slash redirect inlinks ~3 /encryption/, /kms/, /proxy/ 308 + redirect-only inlinks Folds into #2

Not this repo: the dashboard.cipherstash.com root 404 belongs in apps/dashboard; status./trust./clerk. warnings (HTML-lang, large CSS, not-compressed, JS redirect) are third-party vendor pages.

1. Open Graph — add og:url + og:type (172) — P1

Every page has og:title/description/image + twitter:*; only og:url and og:type are missing. Next.js App Router does not inherit a parent openGraph into a child that defines its own, so set these where each page's openGraph is built:

openGraph: {
  type: 'article',      // 'website' for section index / landing pages
  url: pageUrl,         // <-- ADD: absolute URL of this page
  title, description, images: [ogImageUrl],
},

2. Canonical — add it (none exist) — P1

No <link rel="canonical"> on any docs page. Root of the duplicate error (/reference/stack/latest vs /reference/stack/latest/). Fix via the Metadata API and enforce one trailing-slash form:

alternates: { canonical: pageUrl },   // + set `trailingSlash` in next.config and canonicalize to it

Compute pageUrl once and reuse for both #1 and #2.

3. Titles too short / broken (76) — P1/P2

(a) Broken section-index titles — render as uppercased slug with a leading slash:

URL Current Should be
/docs/stack/cipherstash/encryption /ENCRYPTION Encryption
/docs/stack/cipherstash/proxy /PROXY Proxy

Looks like a fallback doing '/' + segment.toUpperCase() when an index page has no explicit title. Give these real titles and fix the fallback.

(b) Generic "Overview"/reference/use-cases and /reference/comparisons both title as Overview. Make them page-specific.

(c) Short single tokens (bulk) — 46 are auto-generated API-reference pages (/reference/stack/latest/packages/...) titled with the bare symbol (SearchTerm, Client, ...); the rest are content pages (Keysets, ZeroKMS, ...). Add a global template in the docs root layout:

title: { template: '%s | CipherStash Docs', default: 'CipherStash Docs' }

For the API-ref generator prefer a kind-aware title, e.g. SearchTerm — @cipherstash/stack API.

4. Meta descriptions too short (122) — P2

Shortest are all auto-generated API reference for X (25-45 chars). Fix in the API-ref generator by building from the symbol's TSDoc summary:

{KIND} {Name} in @cipherstash/stack — {first sentence of TSDoc}. Parameters, return type, and examples.

For hand-authored pages, editorial pass to bring description into the 110-160 char range. (Meta-description too long is a marketing-site issue — 30 of 32; ignore here.)

5-7 (P2/P3)

  • /docs index: add a <title>.
  • /docs/stack/cipherstash/proxy/encrypt-tool: TTFB ~6.4s (others <1s) — profile SSR/data fetch.
  • Trailing-slash variants (/encryption/, /kms/, /proxy/) resolve with the chore(deps): bump next from 16.1.6 to 16.2.3 #2 policy.

Suggested sequencing

  1. Metadata PR: OG url+type (chore(deps): bump next from 16.1.6 to 16.1.7 #1) + canonical (chore(deps): bump next from 16.1.6 to 16.2.3 #2) + title.template & index-title fix (#3a/b) — ~180 page-issues.
  2. Generator PR: API-ref title + description templates (#3c, docs: add CREATE INDEX guide for encrypted columns (self-hosted vs Supabase paths differ) #4) — ~46 titles + ~100 descriptions.
  3. Content pass: ~25 hand-authored content pages.
  4. Spot fixes: docs: per-query-type pages for searchable encryption (Equality / Match / Range) #5, docs: standalone Bulk operations page for the Encryption SDK #6.

Appendix — affected-URL examples

The complete 76-title list (grouped by fix path) and the meta-description list live in the marketing team's audit handoff doc (cipherstash-docs-seo-handoff.md); ping for the raw Ahrefs CSVs if useful. Key examples:

  • Broken: /ENCRYPTION (/cipherstash/encryption), /PROXY (/cipherstash/proxy)
  • Generic: Overview (/reference/use-cases, /reference/comparisons)
  • Content (sample): ZeroKMS, Keysets, Supabase, DynamoDB, Drizzle ORM, SST, Deploy, Billing, Glossary, Members
  • API-ref (46, sample): SearchTerm, Client, CastAs, EncryptOptions, LockContext, encryptedTable, queryTypes

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions