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>
95 lines
4.1 KiB
Markdown
95 lines
4.1 KiB
Markdown
# 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_title` and `meta_description` filled
|
|
- **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:image` MUST 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:image` MUST reference `default_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.xml` is 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.xml` on the next request
|
|
|
|
#### Scenario: Robots references the sitemap
|
|
|
|
- **WHEN** `/robots.txt` is requested
|
|
- **THEN** the response MUST be `text/plain`
|
|
- **AND** MUST contain the absolute `/sitemap.xml` URL
|
|
|
|
### 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 `Organization` MUST 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_enabled` is 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_enabled` is true and a snippet is configured
|
|
- **WHEN** a public page is rendered
|
|
- **THEN** the snippet MUST be present exactly once
|