Archive completed public-site and production-provider changes into main specs, remove duplicate active changes, and add complete-foundation-parity so Phase 0 staging and remaining foundation gaps block Phase 2 cleanly. Co-authored-by: Cursor <cursoragent@cursor.com>
4.1 KiB
public-seo Specification
Purpose
Define per-page SEO metadata, Open Graph, sitemap, robots, structured data, and conditional analytics for public routes.
Requirements
Requirement: Every public page emits title, description and canonical
The system SHALL render a unique <title>, a <meta name="description"> and a <link rel="canonical"> on every public route (SPEC §6.6, §19). Portfolio cases MUST use meta_title/meta_description when filled and fall back to title/summary otherwise. Pages without page-level metadata MUST fall back to default_meta_title and default_meta_description from site_settings.
Scenario: Page-level metadata overrides defaults
- GIVEN a published case with
meta_titleandmeta_descriptionfilled - WHEN a visitor loads the case detail
- THEN the rendered title and description MUST use the case values
Scenario: Missing metadata falls back to site defaults
- GIVEN a published case without
meta_title - WHEN a visitor loads the case detail
- THEN the rendered title MUST be derived from the case title
- AND the description MUST fall back to the case summary or the site default
Scenario: Canonical points to the absolute route URL
- WHEN any public page is rendered
- THEN the canonical URL MUST be the absolute URL of that route without query parameters
Requirement: Open Graph metadata is emitted for sharing
The system SHALL emit Open Graph tags (og:title, og:description, og:type, og:url, og:image) on public pages. The image MUST use the page cover image when available and default_og_image_path from site_settings otherwise.
Scenario: Case detail uses its cover as OG image
- GIVEN a published case with a cover image
- WHEN the case detail is rendered
- THEN
og:imageMUST reference the case cover image URL
Scenario: Pages without cover use the default OG image
- WHEN a page without its own image is rendered
- THEN
og:imageMUST referencedefault_og_image_path
Requirement: Sitemap and robots are served by the application
The system SHALL serve /sitemap.xml listing the home, institutional routes, the services listing, the portfolio listing and every published case slug with its last modification date. /robots.txt MUST be served by an application route referencing the sitemap URL.
Scenario: Sitemap contains only published slugs
- GIVEN one published case and one draft case
- WHEN
/sitemap.xmlis requested - THEN the response MUST include the published slug
- AND MUST NOT include the draft slug
Scenario: Newly published case enters the sitemap
- WHEN an admin publishes a case
- THEN the case slug MUST appear in
/sitemap.xmlon the next request
Scenario: Robots references the sitemap
- WHEN
/robots.txtis requested - THEN the response MUST be
text/plain - AND MUST contain the absolute
/sitemap.xmlURL
Requirement: Basic structured data is emitted where applicable
The system SHALL emit JSON-LD structured data: Organization on the home using site_settings, and Article or equivalent creative work on the case detail.
Scenario: Home exposes organization data
- WHEN the home is rendered
- THEN a JSON-LD block of type
OrganizationMUST be present with brand name and contact data
Scenario: Structured data is valid JSON
- WHEN any public page emits JSON-LD
- THEN the script content MUST parse as valid JSON
Requirement: Analytics script is injected only when explicitly enabled
The system SHALL render the analytics snippet from site_settings only when analytics_enabled is true and the script field is non-empty (WEB-06). Analytics MUST be disabled by default.
Scenario: Analytics disabled emits nothing
- GIVEN
analytics_enabledis false - WHEN any public page is rendered
- THEN the analytics snippet MUST NOT appear in the HTML
Scenario: Analytics enabled injects the configured snippet
- GIVEN
analytics_enabledis true and a snippet is configured - WHEN a public page is rendered
- THEN the snippet MUST be present exactly once