Search Visibility Reference
Target Agents
manager-develop — applies these rules while implementing pages, routes, and serialized metadata output
/moai review — pre-ship indexing review surface; equivalently available as a per-spawn Agent(general-purpose) with web-output review instructions
Core Principle
A page is indexed on what a machine can fetch and parse, not on what a person sees
once everything has finished loading. Two failures dominate everything below: the
same content reachable at more than one address, and a claim asserted in serialized
data that nothing on the page corroborates. Every rule here is a specialization of
one of those two.
Figures that engines publish and then quietly revise — truncation widths, ranking
weights, crawl allowances — are deliberately absent. Where a limit matters, this
reference gives the decision rule and the measurement to take, so the guidance
outlives the figure.
Document Semantics
The structure a parser reads before it reaches any content signal. Cheapest class of
defect to introduce, and the cheapest to detect.
| Rule |
How to check it |
Failure it prevents |
Exactly one h1 per document |
Count h1 elements in the rendered output of each page |
Competing topic signals leave the subject ambiguous |
| No skipped heading level |
Walk the heading sequence; each descent moves by at most one level |
Section nesting a parser cannot reconstruct |
Every image carries alt text |
Each image element has a non-empty alt attribute, or an empty one paired with an explicit decorative marker |
Meaning locked inside a binary the parser cannot open |
| Anchor text names its destination |
Read each link's text with the surrounding sentence hidden, then ask where it goes |
Instruction-shaped link text carries no signal about the target |
| Fragment targets resolve |
For each in-page fragment link, confirm a matching identifier exists in the same rendered output |
Navigation that silently lands nowhere |
Derive alt text from what the image depicts, never from its file name. Where an
image genuinely adds nothing a reader needs, mark it decorative on purpose instead
of inventing a description for it.
Identity and Canonical Address
One resource, one address; everything else redirects to it.
| Decision |
Rule |
| Which address is canonical |
Pick one form per resource and declare it. The declared value must equal the address actually served |
| Trailing-slash variants |
Choose one form and redirect the other permanently. Serving both splits one resource across two addresses |
| Case, query parameters, tracking suffixes |
Normalize before serving. A parameter that does not change the response must not mint a second address |
| Parameterized routes |
Build the declared value from the resolved parameters, never from a fixed string shared across the whole route |
| Retiring an address |
Redirect permanently from the old form and update the declaration in the same change |
A declaration pointing at an address that redirects, errors, or serves different
content is worse than no declaration at all: it actively steers the reader away from
the page it appears on.
Per-Page Metadata
| Field |
Rule |
Recurring defect |
title |
Unique per page, composed from a page-specific part plus a stable identifier for the site |
The scaffold's default survives to production on every route |
| meta description |
Unique per page, written for a person reading a result snippet |
One description copied everywhere, or prose that describes nothing |
| Indexing directive |
Set a default for public pages, then narrow per page class. Authenticated areas and internal tools are excluded deliberately, not merely left unlinked |
A private surface gets indexed because nothing declared otherwise |
| Social preview fields |
Present and absolute. A relative address in a field a third party fetches resolves against the wrong host |
A share card that renders blank off-site |
Length budgets are a decision rule, not a constant: draft the text, observe where the
consuming surface truncates it, then cut to what you observed. Placeholder detection
belongs in the pre-ship check — scan the emitted output for the scaffold's own
default strings and fail when one survives.
Structured Data
Serialized JSON-LD states what a page is. Its one hard constraint is correspondence
with the visible page.
| Decision |
Rule |
| Which types to emit |
Derive them from the page's actual role — an organization, a product, a piece of software, a place, an article. Emit the site-level type everywhere and the page-specific type only where it applies |
| Required fields |
Each type has a small mandatory core. Emit that core completely rather than emitting several types partially |
| Addresses inside the data |
Absolute, and resolving. Relative values are discarded without a warning |
| Several entities on one page |
Emit one block holding all of them, give each a stable identifier, and cross-reference by identifier instead of nesting duplicate copies |
| Placement |
Keep each type on the page it describes. A type repeated site-wide dilutes rather than reinforces |
Mirroring rule — every claim in the serialized data needs a visible counterpart on
the same page. A name, a description, a rating, or a question-and-answer pair that
exists only in the markup is an assertion the reader cannot check, and it is now
scored as a negative signal rather than a neutral one.
Entity Consistency
Structured data identifies an entity. Consistency is what makes that identity hold
across independent sources.
| Surface |
Requirement |
| Canonical name |
One spelling, one casing, one suffix — the same in the serialized data, the page copy, and the headings |
| Owned profile links |
Restricted to accounts this entity itself administers, each confirmed to resolve rather than bounce to a sign-in wall |
| Contact details |
Name, address, and contact number identical across the serialized data, the visible page, and any third-party listings |
| Omission over approximation |
A field with no accurate value is left out. A partial or invented one fragments the identity it was meant to establish |
Formatting differences that read as trivial to a person are distinct values to a
matcher. Fix one format per field and hold it everywhere the entity appears.
Host-Derived Crawl Artifacts
robots.txt and sitemap.xml are output the site serves, not files carried between
environments.
| Artifact |
Rule |
robots.txt |
Derive the absolute addresses it contains from the incoming request's host, so every environment serves a correct description of itself |
sitemap.xml |
Enumerate the addresses actually served. An entry for a removed page spends crawl attempts and signals staleness |
| Staying in sync |
Adding or removing a route updates the enumeration in the same change. A hand-maintained list drifts within one iteration |
| Per-entry hints |
Freshness and weighting hints attached to entries are advisory and widely discounted. Correctness of the address set matters far more than the hints on it |
Hardcoding one environment's host into either artifact is the defect that keeps
recurring, because it passes review while being correct in exactly one place.
Delivery Chokepoints
| Concern |
Rule |
| Response headers |
Apply them at a single wrapping point every response passes through, error and redirect responses included. A header set only on the success path is missing exactly when a reader is most likely to be misled |
| Rendering mode |
Treat "what does a reader receive in the first response?" as a question the project answers explicitly. Content that appears only after client-side work is content a non-executing reader may never see |
| Redirect chains |
One hop to the canonical address. Each extra hop is another opportunity to lose the reader |
| Third-party origins |
Connection hints for origins the page depends on belong ahead of the resources that use them. Measure the effect on this project's own pages rather than assuming one |
Common Rationalizations
| Rationalization |
Reality |
| "The framework already sets sensible metadata defaults" |
A default is identical on every route by construction. Uniqueness is the entire purpose of the field, so it cannot be inherited. |
| "Both address forms serve the same page, so it makes no difference" |
Two addresses serving one resource are two resources to a crawler. Which one survives is guessed unless a declaration and a redirect settle it deliberately. |
| "Serialized data may say more than the page shows — it is extra context" |
Data with no visible counterpart is an unverifiable claim, and it is scored as one. |
"Heading structure and alt text belong to accessibility, not to indexing" |
They are the parse structure a machine uses to segment and describe a page. Sharing a mechanism with accessibility is not a reason to defer them. |
| "The address enumeration can be updated in a follow-up change" |
A list maintained apart from the routes drifts immediately and then silently. Deriving it from the routes is the only version that stays true. |
| "The staging host in the artifact is fine, we swap it at deploy" |
A hardcoded host is right in one environment and wrong in every other, including the one that gets indexed. |
Red Flags
- Two or more
h1 elements on one page, or a heading sequence that skips a level
- A canonical declaration that does not match the address actually serving the page
- The same
title or meta description reused across more than one route
- An image with no
alt attribute, or alt text that repeats the file name
- Link text that reads as an instruction instead of naming a destination
- An in-page fragment link with no matching identifier in the rendered output
robots.txt or sitemap.xml carrying a hardcoded host instead of one derived from the request
- Serialized JSON-LD asserting a name, description, or offer with no visible counterpart
- A relative address in a field a third party fetches — a share-preview image, a structured-data logo
- Response headers applied on the success path only, leaving redirect and error responses uncovered
Verification
1---2name: moai-ref-seo3description: Search-visibility and crawlability reference for web output: canonical URL discipline, per-page title and meta description uniqueness, robots.txt and sitemap.xml as host-derived artifacts, JSON-LD structured data with entity consistency, and the document-semantics rules that decide whether a machine can read a page at all. Agent-extending skill that amplifies web-output implementation and pre-ship review with production-grade indexing and structured-data patterns. NOT for: keyboard operability, visible focus indicators, and form-control labeling (accessibility owns those; delegated to the accessibility surface); generative-engine optimization, deliberately excluded as insufficiently settled; visual polish and interface detail (see moai-ref-ui-polish); API contract design (see moai-ref-api-patterns); security headers and hardening (see moai-ref-owasp-checklist and moai-ref-secops).4---5
6# Search Visibility Reference
7
8## Target Agents
9
10- `manager-develop` — applies these rules while implementing pages, routes, and serialized metadata output
11- `/moai review` — pre-ship indexing review surface; equivalently available as a per-spawn `Agent(general-purpose)` with web-output review instructions
12
13## Core Principle
14
15A page is indexed on what a machine can fetch and parse, not on what a person sees
16once everything has finished loading. Two failures dominate everything below: the
17same content reachable at more than one address, and a claim asserted in serialized
18data that nothing on the page corroborates. Every rule here is a specialization of
19one of those two.
20
21Figures that engines publish and then quietly revise — truncation widths, ranking
22weights, crawl allowances — are deliberately absent. Where a limit matters, this
23reference gives the decision rule and the measurement to take, so the guidance
24outlives the figure.
25
26## Document Semantics
27
28The structure a parser reads before it reaches any content signal. Cheapest class of
29defect to introduce, and the cheapest to detect.
30
31| Rule | How to check it | Failure it prevents |
32|---|---|---|
33| Exactly one `h1` per document | Count `h1` elements in the rendered output of each page | Competing topic signals leave the subject ambiguous |
34| No skipped heading level | Walk the heading sequence; each descent moves by at most one level | Section nesting a parser cannot reconstruct |
35| Every image carries `alt` text | Each image element has a non-empty `alt` attribute, or an empty one paired with an explicit decorative marker | Meaning locked inside a binary the parser cannot open |
36| Anchor text names its destination | Read each link's text with the surrounding sentence hidden, then ask where it goes | Instruction-shaped link text carries no signal about the target |
37| Fragment targets resolve | For each in-page fragment link, confirm a matching identifier exists in the same rendered output | Navigation that silently lands nowhere |
38
39Derive `alt` text from what the image depicts, never from its file name. Where an
40image genuinely adds nothing a reader needs, mark it decorative on purpose instead
41of inventing a description for it.
42
43## Identity and Canonical Address
44
45One resource, one address; everything else redirects to it.
46
47| Decision | Rule |
48|---|---|
49| Which address is canonical | Pick one form per resource and declare it. The declared value must equal the address actually served |
50| Trailing-slash variants | Choose one form and redirect the other permanently. Serving both splits one resource across two addresses |
51| Case, query parameters, tracking suffixes | Normalize before serving. A parameter that does not change the response must not mint a second address |
52| Parameterized routes | Build the declared value from the resolved parameters, never from a fixed string shared across the whole route |
53| Retiring an address | Redirect permanently from the old form and update the declaration in the same change |
54
55A declaration pointing at an address that redirects, errors, or serves different
56content is worse than no declaration at all: it actively steers the reader away from
57the page it appears on.
58
59## Per-Page Metadata
60
61| Field | Rule | Recurring defect |
62|---|---|---|
63| `title` | Unique per page, composed from a page-specific part plus a stable identifier for the site | The scaffold's default survives to production on every route |
64| meta description | Unique per page, written for a person reading a result snippet | One description copied everywhere, or prose that describes nothing |
65| Indexing directive | Set a default for public pages, then narrow per page class. Authenticated areas and internal tools are excluded deliberately, not merely left unlinked | A private surface gets indexed because nothing declared otherwise |
66| Social preview fields | Present and absolute. A relative address in a field a third party fetches resolves against the wrong host | A share card that renders blank off-site |
67
68Length budgets are a decision rule, not a constant: draft the text, observe where the
69consuming surface truncates it, then cut to what you observed. Placeholder detection
70belongs in the pre-ship check — scan the emitted output for the scaffold's own
71default strings and fail when one survives.
72
73## Structured Data
74
75Serialized JSON-LD states what a page *is*. Its one hard constraint is correspondence
76with the visible page.
77
78| Decision | Rule |
79|---|---|
80| Which types to emit | Derive them from the page's actual role — an organization, a product, a piece of software, a place, an article. Emit the site-level type everywhere and the page-specific type only where it applies |
81| Required fields | Each type has a small mandatory core. Emit that core completely rather than emitting several types partially |
82| Addresses inside the data | Absolute, and resolving. Relative values are discarded without a warning |
83| Several entities on one page | Emit one block holding all of them, give each a stable identifier, and cross-reference by identifier instead of nesting duplicate copies |
84| Placement | Keep each type on the page it describes. A type repeated site-wide dilutes rather than reinforces |
85
86**Mirroring rule** — every claim in the serialized data needs a visible counterpart on
87the same page. A name, a description, a rating, or a question-and-answer pair that
88exists only in the markup is an assertion the reader cannot check, and it is now
89scored as a negative signal rather than a neutral one.
90
91## Entity Consistency
92
93Structured data identifies an entity. Consistency is what makes that identity hold
94across independent sources.
95
96| Surface | Requirement |
97|---|---|
98| Canonical name | One spelling, one casing, one suffix — the same in the serialized data, the page copy, and the headings |
99| Owned profile links | Restricted to accounts this entity itself administers, each confirmed to resolve rather than bounce to a sign-in wall |
100| Contact details | Name, address, and contact number identical across the serialized data, the visible page, and any third-party listings |
101| Omission over approximation | A field with no accurate value is left out. A partial or invented one fragments the identity it was meant to establish |
102
103Formatting differences that read as trivial to a person are distinct values to a
104matcher. Fix one format per field and hold it everywhere the entity appears.
105
106## Host-Derived Crawl Artifacts
107
108`robots.txt` and `sitemap.xml` are output the site serves, not files carried between
109environments.
110
111| Artifact | Rule |
112|---|---|
113| `robots.txt` | Derive the absolute addresses it contains from the incoming request's host, so every environment serves a correct description of itself |
114| `sitemap.xml` | Enumerate the addresses actually served. An entry for a removed page spends crawl attempts and signals staleness |
115| Staying in sync | Adding or removing a route updates the enumeration in the same change. A hand-maintained list drifts within one iteration |
116| Per-entry hints | Freshness and weighting hints attached to entries are advisory and widely discounted. Correctness of the address set matters far more than the hints on it |
117
118Hardcoding one environment's host into either artifact is the defect that keeps
119recurring, because it passes review while being correct in exactly one place.
120
121## Delivery Chokepoints
122
123| Concern | Rule |
124|---|---|
125| Response headers | Apply them at a single wrapping point every response passes through, error and redirect responses included. A header set only on the success path is missing exactly when a reader is most likely to be misled |
126| Rendering mode | Treat "what does a reader receive in the first response?" as a question the project answers explicitly. Content that appears only after client-side work is content a non-executing reader may never see |
127| Redirect chains | One hop to the canonical address. Each extra hop is another opportunity to lose the reader |
128| Third-party origins | Connection hints for origins the page depends on belong ahead of the resources that use them. Measure the effect on this project's own pages rather than assuming one |
129
130<!-- moai:evolvable-start id="rationalizations" -->
131## Common Rationalizations
132
133| Rationalization | Reality |
134|---|---|
135| "The framework already sets sensible metadata defaults" | A default is identical on every route by construction. Uniqueness is the entire purpose of the field, so it cannot be inherited. |
136| "Both address forms serve the same page, so it makes no difference" | Two addresses serving one resource are two resources to a crawler. Which one survives is guessed unless a declaration and a redirect settle it deliberately. |
137| "Serialized data may say more than the page shows — it is extra context" | Data with no visible counterpart is an unverifiable claim, and it is scored as one. |
138| "Heading structure and `alt` text belong to accessibility, not to indexing" | They are the parse structure a machine uses to segment and describe a page. Sharing a mechanism with accessibility is not a reason to defer them. |
139| "The address enumeration can be updated in a follow-up change" | A list maintained apart from the routes drifts immediately and then silently. Deriving it from the routes is the only version that stays true. |
140| "The staging host in the artifact is fine, we swap it at deploy" | A hardcoded host is right in one environment and wrong in every other, including the one that gets indexed. |
141
142<!-- moai:evolvable-end -->
143
144<!-- moai:evolvable-start id="red-flags" -->
145## Red Flags
146
147- Two or more `h1` elements on one page, or a heading sequence that skips a level
148- A canonical declaration that does not match the address actually serving the page
149- The same `title` or meta description reused across more than one route
150- An image with no `alt` attribute, or `alt` text that repeats the file name
151- Link text that reads as an instruction instead of naming a destination
152- An in-page fragment link with no matching identifier in the rendered output
153- `robots.txt` or `sitemap.xml` carrying a hardcoded host instead of one derived from the request
154- Serialized JSON-LD asserting a name, description, or offer with no visible counterpart
155- A relative address in a field a third party fetches — a share-preview image, a structured-data logo
156- Response headers applied on the success path only, leaving redirect and error responses uncovered
157
158<!-- moai:evolvable-end -->
159
160<!-- moai:evolvable-start id="verification" -->
161## Verification
162
163- [ ] Every rendered page has exactly one `h1` and a heading sequence with no skipped level — report the counts observed, not the intent
164- [ ] Each route's canonical declaration was fetched and returned that same page with no redirect in between
165- [ ] `title` and meta description were collected across all routes; no duplicates and no scaffold default strings remain
166- [ ] Every image element has an `alt` attribute, and decorative images are marked as such on purpose
167- [ ] Every in-page fragment link was resolved against the rendered output of the page carrying it
168- [ ] `robots.txt` and `sitemap.xml` were fetched from the deployed host and every address in them resolves — quote the address set observed
169- [ ] Serialized JSON-LD parses, its addresses are absolute, and each claim it makes has a visible counterpart quoted from the page
170- [ ] Response headers were observed on a success response, on a redirect response, and on an error response
171
172<!-- moai:evolvable-end -->