Files
MNQ-Catering-y-Evento/docs/seo-audit/mnqcatering.com-audit/findings/schema.md
T
manuyasm87 b084aeff6e fix(seo): exclude noindex aviso-legal page from sitemap; add full audit report
Sitemap filter was excluding cookies/ and privacidad/ but not
aviso-legal/, even though that page is marked noindex,follow -- a
noindexed page has no business being listed for crawl discovery.

Also adds docs/seo-audit/mnqcatering.com-audit/ -- the full multi-agent
SEO audit run this session against the corrected live domain
(www.mnqeventos.es), including the FULL-AUDIT-REPORT.md, ACTION-PLAN.md,
and per-category findings files.
2026-09-15 10:19:00 +02:00

162 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Structured Data (JSON-LD) Audit — mnqcatering.com
**Audited:** 2026-09-15
**Method:** Source review (`src/layouts/MainLayout.astro`, `src/pages/*.astro`) cross-checked against the actual SSR output. Live `https://www.mnqcatering.com` was not directly reachable from this environment (no outbound DNS/network), so the site's own Node/Astro SSR server (`@astrojs/node`, `output: 'server'`) was built and run locally against the committed HEAD (`0e242a6`, working tree clean for `src/`), and pages were requested with `Host: www.mnqcatering.com` to reproduce byte-identical rendered HTML for `/`, `/bodas-reales`, `/corporativo-business`, `/reuniones-familiares`, `/contacto`. This is equivalent to fetching the live pages for structured-data purposes since output is fully server-rendered (no client-side JSON-LD injection to compare against — `raw` and `rendered` HTML are the same thing here).
## Summary
Every page emits one sitewide JSON-LD block (business identity) via `MainLayout.astro`, plus a page-specific `FAQPage` block on four pages. Both blocks are syntactically valid JSON, use `https://schema.org` as `@context`, and (for FAQPage) mirror the visible on-page FAQ content — no invisible/mismatched schema found. However, the sitewide block uses **`@type: "CateringService"`, which is not a valid schema.org type** — this is a critical defect that likely causes the whole block to be ignored or mis-typed by Google and other consumers. There's also a non-standard property (`serviceType`) riding on the invalid type. `BreadcrumbList`, `image`, `priceRange`, `geo`, and `sameAs` are all missing opportunities. FAQPage markup is correctly kept per the current no-rich-result policy (see below).
## Detection Results
| Page | Blocks found |
|---|---|
| `/` (home) | 1× `CateringService` (site-wide) |
| `/bodas-reales` | 1× `CateringService` + 1× `FAQPage` (3 Q&A) |
| `/corporativo-business` | 1× `CateringService` + 1× `FAQPage` (3 Q&A) |
| `/reuniones-familiares` | 1× `CateringService` + 1× `FAQPage` (3 Q&A) |
| `/contacto` | 1× `CateringService` + 1× `FAQPage` (3 Q&A) |
| `/nosotros`, `/aviso-legal`, `/privacidad`, `/cookies` | 1× `CateringService` only (no page-specific schema — expected, no FAQ content on these) |
Format: JSON-LD only (no Microdata/RDFa found), injected via `<script is:inline type="application/ld+json" set:html={JSON.stringify(schema)} />` in `MainLayout.astro:137`. Correctly server-rendered (present in raw HTML, not client-injected).
## Validation Results
### 1. Sitewide business block — `@type: "CateringService"` — **FAIL (Critical)**
**Evidence** (identical on every page, e.g. homepage):
```json
{
"@context": "https://schema.org",
"@type": "CateringService",
"name": "MNQ Catering y Evento",
"url": "https://www.mnqcatering.com",
"description": "Servicio de catering para bodas, eventos corporativos y reuniones familiares con enfoque premium.",
"telephone": "+34 678 17 15 13",
"serviceType": ["Bodas", "Eventos corporativos", "Reuniones familiares"],
"address": { "@type": "PostalAddress", "streetAddress": "Calle Camino Vivero, 6", "postalCode": "29014", "addressLocality": "Málaga", "addressRegion": "Málaga", "addressCountry": "ES" },
"areaServed": [{ "@type": "City", "name": "Málaga" }, { "@type": "AdministrativeArea", "name": "Provincia de Málaga" }],
"contactPoint": { "@type": "ContactPoint", "contactType": "customer service", "telephone": "+34 678 17 15 13", "availableLanguage": ["es"] }
}
```
**Problem:** `CateringService` does not exist in the schema.org vocabulary. The correct type for a catering business is **`CateringBusiness`**, a `LocalBusiness` → `FoodEstablishment` subtype (schema.org/CateringBusiness). Because `CateringService` isn't a recognized type, Google's Rich Results Test / structured data parsers will either flag it as an unrecognized type or silently drop type-specific interpretation, and any generic-type fallback loses `LocalBusiness` inheritance (`address`, `telephone`, `geo`, `openingHoursSpecification`, `priceRange`, `image` all become meaningless without a valid business type backing them).
A second, compounding issue: `serviceType` is a property of schema.org's `Service` type, not of `LocalBusiness`/`FoodEstablishment`/`CateringBusiness`. Even after fixing the `@type`, `serviceType` would still be an out-of-vocabulary property on this type. The three service lines (Bodas, Eventos corporativos, Reuniones familiares) are better modeled as `makesOffer` → `Offer.itemOffered` → `Service` entities, or dropped from the top-level entity and left for each landing page's own content/FAQ.
**Recommendation — fix `@type` to `CateringBusiness` and restructure services in `MainLayout.astro`:**
```json
{
"@context": "https://schema.org",
"@type": "CateringBusiness",
"name": "MNQ Catering y Evento",
"url": "https://www.mnqcatering.com",
"description": "Servicio de catering para bodas, eventos corporativos y reuniones familiares con enfoque premium.",
"telephone": "+34678171513",
"priceRange": "$$$",
"image": "https://www.mnqcatering.com/images/foto-bodas.jpg",
"address": {
"@type": "PostalAddress",
"streetAddress": "Calle Camino Vivero, 6",
"postalCode": "29014",
"addressLocality": "Málaga",
"addressRegion": "Málaga",
"addressCountry": "ES"
},
"areaServed": [
{ "@type": "City", "name": "Málaga" },
{ "@type": "AdministrativeArea", "name": "Provincia de Málaga" }
],
"contactPoint": {
"@type": "ContactPoint",
"contactType": "customer service",
"telephone": "+34678171513",
"availableLanguage": ["es"]
},
"makesOffer": [
{ "@type": "Offer", "itemOffered": { "@type": "Service", "name": "Catering para bodas" } },
{ "@type": "Offer", "itemOffered": { "@type": "Service", "name": "Catering para eventos corporativos" } },
{ "@type": "Offer", "itemOffered": { "@type": "Service", "name": "Catering para reuniones familiares" } }
]
}
```
Notes on this snippet:
- `telephone` normalized to E.164 (`+34678171513`, no spaces) — more consistently parsed than `"+34 678 17 15 13"` (minor, non-blocking, but worth fixing while editing this block anyway).
- `priceRange` is a placeholder pattern (`"$$$"`) — replace with a real value the business is comfortable publishing, or omit if there's no defensible public price band yet. Do **not** ship a placeholder string.
- `image` currently points at an existing OG image already used elsewhere in the layout (`ogImage` default) — reuse rather than invent a new asset, but confirm it visually represents the business (a plated dish or venue photo would be preferable to a wedding hero if one exists).
- `geo`, `sameAs`, `aggregateRating` intentionally omitted here — see dedicated sections below (all pending real data, not fabricated).
### 2. `FAQPage` on 4 pages — **PASS (structurally), Info-priority per current Google policy**
**Evidence:** `bodas-reales.astro`, `corporativo-business.astro`, `reuniones-familiares.astro`, `contacto.astro` each emit a `FAQPage` with 3 `Question`/`acceptedAnswer` pairs. Checked against rendered output — all 3 Q&A pairs on each page are also rendered as visible `<article class="faq-item">` content in the same page, so there's no hidden/mismatched-content violation.
**Structural validation:**
- ✅ `@context: "https://schema.org"`
- ✅ `@type: "FAQPage"`, nested `Question`/`Answer` types correct
- ✅ Required properties present (`mainEntity`, `name`, `acceptedAnswer.text`)
- ✅ No placeholder text
- ✅ Content matches visible page content
**Policy note (per current Google guidance):** Google retired FAQ rich results for all sites (May 7, 2026), so this markup no longer produces a SERP rich result. Per current guidance this is **Info priority, not a defect** — keep it in place, since it still aids AI/LLM citation and entity/answer extraction (ChatGPT, Perplexity, Gemini, AI Overviews). No action required; do not remove it, and do not add more `FAQPage` blocks expecting SERP benefit — any future FAQ-style page should be evaluated as GEO/AI-visibility content, not a rich-result play.
One structural nit worth a look: these are genuinely marketing FAQ content (pricing/coverage questions the business anticipates), not a user-generated Q&A page, so `FAQPage` (not `QAPage`) is the correct type choice here — no change needed.
### 3. Missing: `BreadcrumbList` — **Opportunity (Medium)**
**Evidence:** No `BreadcrumbList` found on any page. Google supports breadcrumb rich results (still active, not deprecated), and the site has a clear, shallow hierarchy (`Home > Bodas Reales`, `Home > Corporativo & Business`, `Home > Reuniones Familiares`, `Home > Contacto`) that maps directly to nav (`SiteHeader.astro` `activeNav`).
**Recommendation** (example for `/bodas-reales`):
```json
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{ "@type": "ListItem", "position": 1, "name": "Inicio", "item": "https://www.mnqcatering.com/" },
{ "@type": "ListItem", "position": 2, "name": "Bodas Reales", "item": "https://www.mnqcatering.com/bodas-reales" }
]
}
```
Repeat per page with the matching second crumb (`Corporativo & Business` → `/corporativo-business`, `Reuniones Familiares` → `/reuniones-familiares`, `Contacto` → `/contacto`). Cheapest way to add this: compute it in `MainLayout.astro` from the existing `activeNav`/`title` props and pass it into the `structuredDataScripts` array alongside the FAQ block, rather than hand-writing it on each page.
### 4. Missing: `geo` (GeoCoordinates) — **Acknowledged, correctly deferred**
Confirmed absent from the `address` block. Per the known-state note, this is intentionally withheld pending a confirmed Google Business Profile pin rather than an oversight — correct call; do not fabricate lat/long. Once available:
```json
"geo": { "@type": "GeoCoordinates", "latitude": 36.xxxxx, "longitude": -4.xxxxx }
```
### 5. Missing: `sameAs` — **Acknowledged, correctly deferred**
No social profile URLs in the schema, matching the known state (no confirmed profiles yet). Do not add placeholder/guessed URLs. Once Instagram/Facebook/LinkedIn profiles are confirmed live and owned by the business:
```json
"sameAs": ["https://www.instagram.com/<handle>", "https://www.facebook.com/<handle>"]
```
### 6. Missing: `Review` / `AggregateRating` — **Acknowledged, correctly deferred**
No review schema present, matching known state. **Do not add this without real, verifiable reviews** — `AggregateRating`/`Review` markup requires genuine user-submitted reviews per Google's structured data policy; fabricating or estimating a rating is a policy violation that can trigger a manual action. Revisit once real reviews exist (Google Business Profile, testimonials with attribution, etc.).
### 7. Duplicate sitewide block per page — **Info, not a defect**
Every page repeats the full `CateringBusiness`/`CateringService` block rather than referencing a single `@id`-linked entity via `@graph`. This is common and acceptable practice for a small static-ish site (each page is self-contained for crawlers that don't execute JS or follow references), so not flagged as an issue — but if the block grows substantially (e.g., after adding `makesOffer`, `geo`, `sameAs`, `image`), consider consolidating into a `@graph` with `@id` references from per-page `WebPage`/`FAQPage` nodes to avoid repeating a large block on every request. Not urgent.
## Score: 62 / 100
**Deductions:**
- −25: Invalid `@type` (`CateringService` doesn't exist) on the one schema block present on every page of the site — this is the single highest-impact fix available, since it currently likely undermines rich-result/entity recognition for the whole business identity block.
- −5: Non-standard `serviceType` property riding on that invalid type.
- −5: `telephone` not in E.164 format (minor/cosmetic).
- −3: No `BreadcrumbList` despite a hierarchy that trivially supports it.
**Not deducted** (correctly deferred, not defects): missing `geo`, `sameAs`, `Review`/`AggregateRating` — all withheld pending real data, which is the right call rather than fabricating values.
## What already works well
- **JSON-LD only**, no legacy Microdata/RDFa to reconcile — clean baseline.
- **`https://` context**, absolute URLs throughout, ISO-correct address structure.
- **Server-rendered**, present in raw HTML (not client-injected) — fully crawlable without JS execution.
- **FAQ schema content matches visible page content exactly** — no hidden-text schema abuse anywhere.
- **Correct restraint** on `geo`, `sameAs`, and `AggregateRating` — nothing fabricated or guessed to fill gaps, which is exactly right per Google's structured-data policies.
- **No deprecated types used** (no `HowTo`, `SpecialAnnouncement`, `CourseInfo`, etc.).
- Address, `areaServed`, and `contactPoint` are properly structured and consistent across every page.