Compare commits
11 Commits
20d50411aa
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| b12830ad49 | |||
| 4b08a898bf | |||
| a314ab9714 | |||
| d67a540738 | |||
| d17f75c423 | |||
| b084aeff6e | |||
| a6692b7540 | |||
| 0e242a6116 | |||
| 5741e68181 | |||
| 5c0ed95e9e | |||
| 9218c5d483 |
+2
-2
@@ -1,4 +1,4 @@
|
||||
FROM node:20-alpine AS builder
|
||||
FROM node:22-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -9,7 +9,7 @@ COPY . .
|
||||
RUN npm run build
|
||||
|
||||
# ── Runtime ──────────────────────────────────────────
|
||||
FROM node:20-alpine AS runtime
|
||||
FROM node:22-alpine AS runtime
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
+5
-2
@@ -3,14 +3,17 @@ import node from '@astrojs/node';
|
||||
import sitemap from '@astrojs/sitemap';
|
||||
|
||||
export default defineConfig({
|
||||
site: 'https://www.mnqcatering.com',
|
||||
site: 'https://www.mnqeventos.es',
|
||||
output: 'server',
|
||||
adapter: node({
|
||||
mode: 'standalone',
|
||||
}),
|
||||
integrations: [
|
||||
sitemap({
|
||||
filter: (page) => !page.endsWith('/cookies/') && !page.endsWith('/privacidad/'),
|
||||
filter: (page) =>
|
||||
!page.endsWith('/cookies/') &&
|
||||
!page.endsWith('/privacidad/') &&
|
||||
!page.endsWith('/aviso-legal/'),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# Plan de acción priorizado — MNQ Catering y Evento
|
||||
|
||||
## Fase 0 — Ya hecho, pendiente de un solo paso tuyo
|
||||
- [x] Corregir dominio canónico en todo el código (commit `a6692b7`)
|
||||
- [x] Corregir `@type` de schema inválido (`CateringService` → `CateringBusiness`)
|
||||
- [ ] **Desplegar a producción** — sin esto, nada de lo de hoy tiene efecto real. Es el único paso que falta para resolver ~15 hallazgos de golpe (dominio, sitemap, robots, OG, canonical).
|
||||
|
||||
## Fase 1 — Críticos (esta semana)
|
||||
- [ ] Desplegar el commit `a6692b7` (ver Fase 0)
|
||||
- [ ] Quitar `/aviso-legal/` del filtro de exclusión inverso en `astro.config.mjs` (está `noindex` pero sigue en el sitemap)
|
||||
- [ ] Quitar el `<link rel="stylesheet">` duplicado de Google Fonts en `MainLayout.astro` (regresión: re-bloquea el render que el preload ya había resuelto)
|
||||
- [ ] Revisar por qué el CSS de `/aviso-legal` (24KB) se está bundleando sitewide — probable leak de Astro
|
||||
- [ ] Agregar headers de seguridad (HSTS, X-Content-Type-Options, X-Frame-Options, CSP) a nivel de hosting/reverse-proxy
|
||||
- [ ] Habilitar compresión gzip/brotli en el servidor
|
||||
|
||||
## Fase 2 — Alto impacto (próximas 2-3 semanas, necesita datos tuyos)
|
||||
- [ ] Agregar rango de precio ("desde X€ por persona" o similar) — bloqueador #1 de conversión y de citabilidad IA según 3 auditores independientes
|
||||
- [ ] Agregar rango de capacidad (mínimo/máximo de invitados) por tipo de evento
|
||||
- [ ] Agregar antelación mínima de reserva
|
||||
- [ ] Mencionar "Málaga" y zona de cobertura explícitamente en el texto visible de cada página (hoy: cero menciones)
|
||||
- [ ] Unificar "MNQ Catering y Evento" vs "y Eventos" en todo el sitio (header/footer vs. resto)
|
||||
- [ ] Corregir la CSS que retrasa el LCP del `<h1>` del hero (animation-delay de 500ms en `hero-enter-2`)
|
||||
|
||||
## Fase 3 — Contenido y autoridad (mes 2)
|
||||
- [ ] Completar Google Business Profile (dirección, categoría, fotos) — ver `docs/seo-audit/publicacion-buscadores-checklist.md`
|
||||
- [ ] Agregar coordenadas `geo` al schema una vez fijado el pin en GBP
|
||||
- [ ] Agregar perfiles sociales reales (`sameAs`) si existen
|
||||
- [ ] Nombrar y dar credenciales al "Chef Ejecutivo" mencionado (hoy sin nombre ni bio) — señal de E-E-A-T
|
||||
- [ ] Verificar que el venue de testimonio citado ("Castillo de Viñuelas") sea correcto — parece ser de Madrid, no Málaga
|
||||
- [ ] Replicar el patrón FAQ (bueno para AEO) en homepage, no solo en /contacto
|
||||
- [ ] Reescribir el párrafo "cobertura confirmada caso a caso" que se repite casi idéntico en 5 páginas
|
||||
|
||||
## Fase 4 — Monitoreo continuo
|
||||
- [ ] Verificar Search Console tras el deploy (indexación real de las 6 páginas)
|
||||
- [ ] Re-correr Lighthouse tras aplicar Fase 1-2
|
||||
- [ ] Medir `/corporativo-business` con Lighthouse (no se completó en esta auditoría por límite de tiempo)
|
||||
- [ ] Confirmar comportamiento del apex `mnqeventos.es` sin `www` (no respondió durante esta auditoría, verificar redirect)
|
||||
@@ -0,0 +1,81 @@
|
||||
# Auditoría SEO completa — MNQ Catering y Evento
|
||||
|
||||
Fecha: 2026-09-15
|
||||
Sitio auditado: **https://www.mnqeventos.es** (dominio real y registrado)
|
||||
|
||||
## ⚠️ Nota metodológica crítica
|
||||
|
||||
Esta auditoría arrancó apuntando a `mnqcatering.com` porque es el dominio que
|
||||
usa todo el código fuente (`astro.config.mjs`, schema, footer, etc.) y los
|
||||
últimos commits del repo. A mitad de auditoría se confirmó — vía `dig`,
|
||||
`whois` y `curl`/WebFetch independientes — que **`mnqcatering.com` nunca fue
|
||||
registrado**: WHOIS devuelve "No match", no tiene NS, no resuelve. El dominio
|
||||
real, registrado y sirviendo tráfico ahora mismo es **`www.mnqeventos.es`**.
|
||||
|
||||
Se corrigió el rumbo a mitad de auditoría: cada especialista fue re-dirigido
|
||||
contra `www.mnqeventos.es`. El código del repo ya fue corregido en el commit
|
||||
`a6692b7` (dominio canónico + un bug real de schema.org encontrado en la
|
||||
auditoría), **pero ese fix todavía no está desplegado en producción** — el
|
||||
sitio en vivo sigue sirviendo el build viejo, con todas las referencias
|
||||
internas (canonical, OG, JSON-LD, sitemap, robots.txt) apuntando al dominio
|
||||
muerto.
|
||||
|
||||
**Esto es, en sí mismo, el hallazgo más severo de toda la auditoría**: hasta
|
||||
que se despliegue, cada motor de búsqueda y cada IA que respete la
|
||||
canonicalización va a asociar el contenido con una URL que no existe.
|
||||
|
||||
## Resumen ejecutivo
|
||||
|
||||
**SEO Health Score: ~52/100 (estado actualmente en vivo)**
|
||||
|
||||
Este número reflejaría **~65-70/100 solo con desplegar** el fix ya
|
||||
commiteado (dominio + schema), sin escribir una línea de código nueva. El
|
||||
resto (contenido, precios, GBP) sí requiere trabajo adicional.
|
||||
|
||||
| Categoría | Score | Nota |
|
||||
|---|---|---|
|
||||
| Técnico | 58/100 | Bueno en fuente; roto en vivo por el dominio no desplegado |
|
||||
| Contenido | 32/100 | Contenido "thin", cero cifras concretas (precio/capacidad) |
|
||||
| Schema / structured data | 62/100 (medido antes del fix) | `@type` inválido corregido hoy, pendiente de deploy |
|
||||
| Sitemap | 25/100 (en vivo) → ~75 post-deploy | Cadena de sitemap 100% rota en vivo por el dominio |
|
||||
| Performance (Lighthouse real) | ~78-84/100 | Bueno, con un LCP evitable por CSS de animación |
|
||||
| Visual/mobile | No verificable | Limitación de red del sandbox, no defecto del sitio |
|
||||
| AEO / GEO (IA) | 46/100 | Permisivo con AI crawlers, pero sin señales de marca |
|
||||
| SEO local | 22/100 (en vivo) | Sin dirección/ciudad visible en el sitio en vivo hoy |
|
||||
| SXO (experiencia de búsqueda) | 59/100 | Estructura de página correcta, sin ancla de precio/zona |
|
||||
|
||||
### Top 5 críticos
|
||||
1. **El fix de dominio ya commiteado no está desplegado** — deploy pendiente, cero código nuevo necesario.
|
||||
2. **Cero mención de "Málaga" o de la dirección en el sitio en vivo** — ni en texto ni en schema.
|
||||
3. **Cero cifras de precio/capacidad/antelación** en ninguna página — el bloqueador #1 de conversión y de citabilidad por IA.
|
||||
4. `@type: "CateringService"` no es un tipo válido de schema.org — corregido hoy a `CateringBusiness`, pendiente de deploy.
|
||||
5. El sitemap y el robots.txt en vivo apuntan a `sitemap-0.xml` del dominio muerto — cadena de descubrimiento rota.
|
||||
|
||||
### Top 5 quick wins (una vez desplegado)
|
||||
1. Deploy del commit `a6692b7` — resuelve dominio, schema, sitemap, robots.txt, OG de un saque.
|
||||
2. Agregar "Málaga" + rango de precio "desde X€" en homepage y páginas de servicio (bloqueado solo por falta de esos datos reales, no de código).
|
||||
3. Quitar el `<link rel="stylesheet">` duplicado de Google Fonts que re-bloquea el render (regresión de un fix anterior).
|
||||
4. Excluir `/aviso-legal/` del sitemap (ya está `noindex` pero sigue listada).
|
||||
5. Unificar "MNQ Catering y Evento" vs "y Eventos" (inconsistencia de nombre de marca en header/footer vs. resto del sitio).
|
||||
|
||||
## Detalle por categoría
|
||||
|
||||
Cada archivo tiene evidencia completa (curl/dig/whois, snippets, tablas de severidad):
|
||||
|
||||
- [`findings/technical.md`](findings/technical.md) — robots, sitemap, canonicals, headers de seguridad, TLS
|
||||
- [`findings/content.md`](findings/content.md) — E-E-A-T, thin content, citabilidad IA
|
||||
- [`findings/schema.md`](findings/schema.md) — JSON-LD, tipo inválido corregido, recomendaciones
|
||||
- [`findings/sitemap.md`](findings/sitemap.md) — estructura, aviso-legal, trailing slash
|
||||
- [`findings/performance.md`](findings/performance.md) — Lighthouse real, LCP, CSS duplicado
|
||||
- [`findings/visual.md`](findings/visual.md) — bloqueado por limitación de red del sandbox
|
||||
- [`findings/geo.md`](findings/geo.md) — AEO/GEO, llms.txt, AI crawlers
|
||||
- [`findings/local.md`](findings/local.md) — NAP, GBP (pendiente, reconocido), schema local
|
||||
- [`findings/sxo.md`](findings/sxo.md) — SERP real, personas, page-type match
|
||||
|
||||
## Seguridad de headers (hallazgo transversal)
|
||||
|
||||
Ninguna respuesta del sitio en vivo trae HSTS, X-Content-Type-Options,
|
||||
X-Frame-Options, CSP ni Referrer-Policy, y no hay compresión gzip/brotli en
|
||||
el HTML. Esto es de capa de hosting/reverse-proxy, no de código Astro — hay
|
||||
que revisarlo donde esté corriendo el servidor (Docker/nixpacks + lo que sea
|
||||
que haga de proxy delante).
|
||||
@@ -0,0 +1,142 @@
|
||||
# Content Quality Audit — mnqcatering.com
|
||||
|
||||
Scope: E-E-A-T, readability, thin content, duplication, AI citation readiness for the 6 commercial pages (`/`, `/nosotros`, `/bodas-reales`, `/corporativo-business`, `/reuniones-familiares`, `/contacto`).
|
||||
|
||||
## Methodology note / critical caveat
|
||||
|
||||
**The live site could not be fetched during this audit.** `www.mnqcatering.com` and `mnqcatering.com` returned `NXDOMAIN` both from the local resolver and when queried directly against `8.8.8.8` (Google public DNS) on 2026-09-15 — this is a real, non-sandboxed DNS failure, not a tool limitation (general internet access and DNS resolution for other domains worked fine in the same environment). If this is still true in production, it is a **critical, audit-blocking issue in its own right**: no amount of content quality matters if the domain doesn't resolve — Googlebot, ChatGPT/Perplexity crawlers, and users get nothing. This should be verified independently and flagged to whichever category owns DNS/hosting/technical SEO; it is out of scope for this content-only review to diagnose further.
|
||||
|
||||
Given the fetch failure, this audit is based on the **Astro page source** (`src/pages/*.astro`) in the repo, which renders the copy 1:1 (no client-side content injection observed — FAQ arrays and structured data are server-rendered at build time). Word counts below are computed programmatically from the actual rendered text nodes (frontmatter, `<style>`, `<script>`, and JSX expressions stripped), not from source line/byte counts.
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
The site has real trust scaffolding — a genuine street address, a real phone number, `CateringService` + `FAQPage` schema.org markup, legal pages (aviso legal/privacidad/cookies), and what appear to be genuine team/kitchen photos rather than stock imagery. But every one of the 6 commercial pages is **severely thin** (well under half of Google's topical-coverage floor for its page type), the four FAQ blocks are **generic and evasive rather than fact-bearing**, the "coverage is confirmed case-by-case" paragraph is **repeated near-verbatim five times** across five different pages, and — most importantly for AI-citability — **there is still no pricing anchor, no capacity range, and no lead-time/booking-window figure anywhere on the site**. This was flagged as a gap in a prior audit round; based on the current source, it has not been resolved. An AI assistant can correctly confirm "yes, MNQ does weddings in Málaga," but cannot answer any of the three questions a prospective customer (or an AI Overview) actually needs — how much, how many guests, how far in advance to book.
|
||||
|
||||
**Content Quality Score: 32/100**
|
||||
**AI Citation Readiness Score: 24/100**
|
||||
|
||||
---
|
||||
|
||||
## Findings
|
||||
|
||||
### 1. Thin content on every commercial page (HIGH severity)
|
||||
|
||||
Word counts of visible body copy (FAQ text included where present):
|
||||
|
||||
| Page | Words (measured) | Page-type floor | % of floor |
|
||||
|---|---|---|---|
|
||||
| `/` (homepage) | ~287 | 500 | 57% |
|
||||
| `/nosotros` (about — E-E-A-T critical) | ~291 | ~500 (treated as homepage-tier) | 58% |
|
||||
| `/bodas-reales` (service) | ~391 (319 body + 72 FAQ) | 800 | 49% |
|
||||
| `/corporativo-business` (service) | ~387 (307 body + 80 FAQ) | 800 | 48% |
|
||||
| `/reuniones-familiares` (service) | ~414 (347 body + 67 FAQ) | 800 | 52% |
|
||||
| `/contacto` | ~266 (191 body + 75 FAQ) | ~400–500 (lighter bar) | 53–67% |
|
||||
|
||||
None of the three actual service pages reach even 55% of the 800-word topical-coverage floor Google's guidelines describe for service pages. `/nosotros` — the single most important page for demonstrating Experience/Expertise — is barely 291 words and names no one.
|
||||
|
||||
**Recommendation:** Each service page needs genuinine topical expansion, not filler: a real sample menu or format breakdown per service type, 2–3 detailed case studies with real numbers (guest count, venue, service style), and a named chef/team-lead bio with real credentials on `/nosotros`.
|
||||
|
||||
### 2. No pricing, capacity, or lead-time facts anywhere (HIGH severity — this is the prior-round gap, still open)
|
||||
|
||||
Every page repeats variations of "se calcula/valora según..." without ever landing on a number. Representative quotes:
|
||||
|
||||
- Homepage: *"Cada proyecto se valora de forma individual. Confirmamos la viabilidad según la fecha, el número de asistentes, el tipo de servicio y el desplazamiento necesario..."*
|
||||
- `/bodas-reales`: *"Propuesta económica: se calcula según menú, servicio, menaje, tiempos y desplazamiento."*
|
||||
- `/corporativo-business`: *"Presupuesto: siempre se calcula a medida según servicio, menaje, personal y desplazamiento."*
|
||||
- `/reuniones-familiares` FAQ: *"¿Cómo se prepara el presupuesto? Se ajusta al número estimado de invitados, al formato del servicio, al menaje necesario y al desplazamiento previsto."*
|
||||
|
||||
Checked explicitly via grep across all page source: zero occurrences of "€", "precio", "desde [number]", any guest-count range (e.g. "20–500 invitados"), any stated minimum/maximum group size, or any lead-time figure ("reserve con X semanas/meses"). This is unchanged from what a prior audit round already flagged.
|
||||
|
||||
**Why this matters for AI citability:** an AI Overview or ChatGPT answer to "cuánto cuesta un catering de boda en Málaga" or "¿cuántos invitados puede cubrir MNQ?" cannot be constructed from this content — there is nothing to lift. Even an indicative range ("desde 65€/invitado", "de 20 a 400 comensales", "recomendamos reservar con 2–3 meses de antelación") would be directly quotable and would materially change AI-citation odds. Right now the honest answer any AI tool can give is "contact them to find out," which is a weak, low-value citation.
|
||||
|
||||
### 3. FAQ content is generic and non-committal, not fact-bearing (MEDIUM-HIGH severity)
|
||||
|
||||
`FAQPage` schema is present on 4 pages (`bodas-reales`, `corporativo-business`, `reuniones-familiares`, `contacto`) as stated, but the answers dodge the question rather than answering it. Example — `/bodas-reales`:
|
||||
|
||||
> Q: *"¿El presupuesto incluye solo comida?"*
|
||||
> A: *"La propuesta puede contemplar cocina, servicio, menaje y necesidades de montaje, según lo que requiera la celebración."*
|
||||
|
||||
This never actually says yes or no, nor gives a typical inclusion list. Same pattern on `/corporativo-business`:
|
||||
|
||||
> Q: *"¿Cómo valoran la cobertura de un evento de empresa?"*
|
||||
> A: *"La confirmamos según disponibilidad de fecha, desplazamiento, complejidad logística del espacio y necesidades reales de producción."*
|
||||
|
||||
This is the kind of FAQ answer an AI system will *not* select to quote, because it contains no extractable fact — it's a description of a process, not an answer. Compare to what would actually be citable: "Sí, la propuesta incluye siempre cocina y servicio de sala; menaje y decoración se cotizan aparte salvo que se solicite el paquete completo."
|
||||
|
||||
Also note the FAQ *questions* themselves are near-duplicated across pages: "¿Cómo se confirma/valora la cobertura/viabilidad?" appears, reworded, on `/bodas-reales`, `/corporativo-business`, `/reuniones-familiares`, and `/contacto`. "¿Cómo se prepara/calcula el presupuesto?" appears on 3 of the 4. This is templated content with the labels swapped, not four independently useful FAQ sections.
|
||||
|
||||
### 4. Repeated near-duplicate paragraph across 5 pages (MEDIUM severity)
|
||||
|
||||
The "we confirm coverage case-by-case, based on date/location/guest count/format/travel" paragraph is reused, reworded, on every single commercial page:
|
||||
|
||||
- Homepage: *"La cobertura final se valida caso a caso para asegurar montaje, tiempos de servicio y desplazamiento coherentes con el evento."*
|
||||
- `/bodas-reales`: *"Viabilidad: la confirmamos tras revisar disponibilidad y condiciones del espacio."*
|
||||
- `/corporativo-business`: *"Cobertura confirmada: La viabilidad final se valida según desplazamiento, montaje necesario y condicionantes del espacio."*
|
||||
- `/reuniones-familiares`: *"Cobertura confirmada: La viabilidad se revisa caso a caso para asegurar que el espacio y la logística estén alineados con la experiencia que promete MNQ."*
|
||||
- `/contacto`: *"No trabajamos con una cobertura automática ni con propuestas cerradas. Cada servicio se estudia según la ubicación del evento, la fecha disponible, el volumen de invitados y las necesidades reales de montaje o servicio."*
|
||||
|
||||
Individually each is fine; together, five near-identical restatements of the same non-answer read as templated boilerplate rather than distinct topical coverage per page — a pattern the Sept 2025 QRG flags as a low-quality-AI-content marker ("repetitive structure across pages"). The three-card layout (label + one sentence, "we assess X case by case") is also structurally identical across `/` , `/corporativo-business`, and `/reuniones-familiares`.
|
||||
|
||||
### 5. No named expertise signal — "Chef Ejecutivo" is mentioned but never identified (HIGH severity for Expertise/Authoritativeness)
|
||||
|
||||
Grep across all page source for "año", "experiencia", "fundad", "premio", "certificad", "chef" returns exactly one hit: `/reuniones-familiares.astro` line 278, *"Reunión con nuestro Chef Ejecutivo para definir el concepto gastronómico"* — no name, no bio, no photo caption identifying who this is, no prior restaurant/culinary pedigree, no years of experience, no certifications, no press mentions, no awards. `/nosotros` — the page whose entire job is to carry Expertise/Experience signals — never names a single person; the hero photo alt text is *"Responsable de MNQ acompañando la puesta a punto..."* (a role, not a name).
|
||||
|
||||
**Recommendation:** name the chef/founder, state actual years of experience or prior kitchens, add a real bio and headshot with credentials. This is the single highest-leverage E-E-A-T fix available.
|
||||
|
||||
### 6. Testimonials are unverifiable and one is geographically inconsistent with the brand's core claim (MEDIUM severity — needs owner verification)
|
||||
|
||||
Testimonials use first names only, no last names, no photos, no link to a verifiable source (Google Business Profile, Bodas.net, etc.):
|
||||
|
||||
- *"Isabel & Marcos — Boda en Hacienda del Sol"* (homepage)
|
||||
- *"Elena & Carlos — Finca La Montaña, Junio 2023"* / *"Patricia & Javier — Castillo de Viñuelas, Septiembre 2023"* (`/bodas-reales`)
|
||||
|
||||
**Flag for owner verification, not asserted as fact (could not browse to confirm live):** "Castillo de Viñuelas" is, per general knowledge, a well-known events venue in Tres Cantos, near Madrid — not in Málaga province. If MNQ's business is positioned as Málaga-based catering, a testimonial citing a Madrid-region venue either (a) represents a genuine out-of-area booking worth stating explicitly ("also available for destination weddings outside Málaga"), or (b) is placeholder/generic copy not tied to a real MNQ event — which would actively undermine local E-E-A-T if discovered by a prospective client or a fact-checking AI system. This is worth a direct check with the business owner before the next content pass.
|
||||
|
||||
No client company logos are shown on `/corporativo-business` despite claiming galas/lanzamientos "de alto nivel" for named-sounding brands.
|
||||
|
||||
### 7. Readability (LOW severity — this is a genuine strength)
|
||||
|
||||
Spanish sentence structure across all 6 pages is clean: short-to-medium sentences, no dense jargon, no run-on subordinate clauses. Business/process vocabulary ("desplazamiento", "menaje", "montaje", "briefing") is appropriate for the catering-industry audience and not overused. Marketing copy tends toward generic-premium adjectives ("excelencia", "exclusivo", "inolvidable", "de autor") but doesn't cross into incomprehensible fluff. This is the one area needing no changes.
|
||||
|
||||
### 8. What already works well
|
||||
|
||||
- Real, verifiable NAP: phone `+34 678 17 15 13` and street address `Calle Camino Vivero, 6, 29014, Málaga` are consistent across footer, `/contacto`, and `CateringService` schema (`src/layouts/MainLayout.astro`) — good baseline Trustworthiness signal.
|
||||
- `CateringService` + `FAQPage` JSON-LD is correctly implemented and machine-parseable (structured data itself is not the audit's finding — see `geo.md` / technical findings for schema-specific review).
|
||||
- Legal transparency pages exist (`aviso-legal`, `privacidad`, `cookies`) — a real trust signal most small local competitors skip.
|
||||
- Team/kitchen photography on `/nosotros` (`mnq-team-service.jpg`, `mnq-team-kitchen.webp`, etc.) appears to be genuine operational photography rather than stock — a real Experience signal, just uncaptioned with names.
|
||||
- `areaServed` in schema is honestly scoped to Málaga city + province rather than an inflated national claim — a good-faith trust choice, consistent with the git history ("add local business address and area served signals").
|
||||
- Format lists per service type (e.g. bodas: *"cóctel, banquete, recena o una combinación"*; corporate: *"coffee break, cóctel, comida de empresa, cena institucional"*) are concrete and non-generic — these are genuinely useful, specific facts and should be the template for how pricing/capacity/lead-time facts get added.
|
||||
|
||||
---
|
||||
|
||||
## AI-citability test results
|
||||
|
||||
- **"¿Hace MNQ bodas en Málaga?"** — Answerable and citable. `/bodas-reales` + schema `serviceType`/`areaServed` support a clean, correct answer.
|
||||
- **"¿Cómo reservo a MNQ para un evento corporativo?"** — Partially answerable: WhatsApp (`+34 678 17 15 13`), a contact form, and the specific info to send (fecha, ubicación, horario, asistentes, formato) are all stated on `/corporativo-business` and `/contacto`. This is citable at a shallow "how to start" level.
|
||||
- **"¿Cuánto cuesta / cuántos invitados puede cubrir / con cuánta antelación reservar?"** — Not answerable from current content. No price anchor, no capacity range, no lead-time figure exists anywhere in the source. This is the highest-priority content gap for AI citation readiness.
|
||||
|
||||
---
|
||||
|
||||
## Scores
|
||||
|
||||
| Factor | Weight | Score /100 | Notes |
|
||||
|---|---|---|---|
|
||||
| Experience | 20% | 35 | Real team photos, but no named individuals, no dated case studies |
|
||||
| Expertise | 25% | 25 | Unnamed "Chef Ejecutivo," zero credentials/years/certifications anywhere |
|
||||
| Authoritativeness | 25% | 20 | No press, no awards, no third-party review links, one geographically-inconsistent testimonial venue to verify |
|
||||
| Trustworthiness | 30% | 50 | Real NAP + legal pages + schema, undercut by zero pricing transparency and unverifiable testimonials |
|
||||
| **E-E-A-T weighted** | | **33/100** | |
|
||||
|
||||
**Content Quality Score: 32/100**
|
||||
**AI Citation Readiness Score: 24/100**
|
||||
|
||||
## Priority recommendations (highest impact first)
|
||||
|
||||
1. Add at least indicative pricing (a "desde X€/invitado" range or 2–3 worked examples), a capacity range, and a lead-time recommendation to every service page and the FAQ blocks — this is the single biggest lever for both thin-content and AI-citability.
|
||||
2. Name and credential the chef/founder on `/nosotros`; convert the unnamed "Chef Ejecutivo" mention into a real bio.
|
||||
3. Rewrite the 4 FAQ blocks so each answer states a concrete fact/number instead of describing the internal evaluation process.
|
||||
4. De-duplicate the "coverage confirmed case-by-case" paragraph — keep one clear statement of the qualification process (e.g. on `/contacto`) and use the space on service pages for page-specific substance (sample menus, format detail, case studies) instead.
|
||||
5. Verify (with the business owner) the "Castillo de Viñuelas" testimonial location before the next content pass, and add real last names/photos or a link to a verifiable review source for all testimonials.
|
||||
6. Independently confirm whether the DNS `NXDOMAIN` observed for `www.mnqcatering.com` / `mnqcatering.com` during this audit (2026-09-15, confirmed against both local resolver and `8.8.8.8`) reflects current production state — if so, this blocks all crawling/AI-citation regardless of content fixes.
|
||||
@@ -0,0 +1,161 @@
|
||||
# GEO / AI Search Readiness Audit — mnqeventos.es (corrected)
|
||||
|
||||
**Audit date:** 2026-09-15
|
||||
**Live target audited:** https://www.mnqeventos.es
|
||||
**Method:** Live DNS resolution, direct HTTP fetch (curl), SSR-aware rendering via `render_page.py --mode auto`, repository source inspection for the pending (undeployed) fix.
|
||||
|
||||
> **Correction notice:** An earlier pass of this audit targeted `mnqcatering.com`, which is not a registered domain (WHOIS: no match; DNS: NXDOMAIN) — that was a mistake in the audit target, not a finding about the real site, and produced a meaningless 0/100 score. The real production domain is **`https://www.mnqeventos.es`**, confirmed live and resolving (`169.58.117.246`, HTTP/2 200 on `/`, `/robots.txt`, `/llms.txt`). This document replaces that earlier pass entirely.
|
||||
|
||||
## Summary
|
||||
|
||||
The live site is technically reachable and reasonably crawlable (SSR HTML, permissive `robots.txt`, working `llms.txt`), but it is **actively telling every crawler — including every AI crawler in scope — the wrong canonical domain**. The canonical tag, Open Graph tags, JSON-LD entity `url`, the `robots.txt` `Sitemap:` directive, and the visible footer link all point to `mnqcatering.com`, a domain that is unregistered and does not resolve. This is a live, current-production defect (not a legacy leftover in docs) with direct GEO consequences: AI crawlers that respect canonicalization will attribute this content to a dead URL, entity graphs built from the `CateringService` JSON-LD will resolve to a broken `url` field, and `og:image`/`og:url` previews (used by some AI answer engines for citation cards) will fail to load.
|
||||
|
||||
The good news: the root cause is already fixed in the repository (commit `a6692b7 fix(seo): correct canonical domain to www.mnqeventos.es`, plus `astro.config.mjs`, `MainLayout.astro`, `SiteFooter.astro`, `contacto.astro`, `public/robots.txt`, `public/llms.txt` all now reference `mnqeventos.es`) — it just **has not been deployed to production yet**. The live `llms.txt` served today is also a stale, thinner version missing the address and area-served lines that exist in the repo's current `llms.txt`. Once deployed, several of the findings below resolve automatically; I've flagged which ones.
|
||||
|
||||
## Findings
|
||||
|
||||
### 1. Canonical URL, Open Graph, and JSON-LD entity `url` all point to a dead domain — HIGH (live defect, fix pending deploy)
|
||||
- **Severity:** High
|
||||
- **Evidence (live, fetched 2026-09-15):**
|
||||
- `<link rel="canonical" href="https://www.mnqcatering.com/">` on `/`, and `https://www.mnqcatering.com/contacto`, `https://www.mnqcatering.com/nosotros` on those pages respectively.
|
||||
- `<meta property="og:url" content="https://www.mnqcatering.com/">`, `<meta property="og:image" content="https://www.mnqcatering.com/images/foto-bodas.jpg">` — the image URL will fail to load for any consumer (AI citation card, social preview) since `mnqcatering.com` does not resolve.
|
||||
- JSON-LD `CateringService` schema on `/`, `/contacto`, `/nosotros`: `"url":"https://www.mnqcatering.com"`.
|
||||
- Footer visible link: `<a href="https://www.mnqcatering.com">www.mnqcatering.com</a>` on every page — a dead outbound link on a live site.
|
||||
- `umami` analytics script tag: `data-domains="www.mnqcatering.com"` (low GEO impact, but confirms the whole template still ships the old domain).
|
||||
- **Impact:** AI/search crawlers that respect `rel=canonical` will index/attribute content to a non-existent URL, which can suppress citation entirely or cause AI engines to silently drop the page from consideration when the canonical target 404s/fails to resolve. Structured-data entity resolution (how Google/Bing/AI systems build a knowledge-graph node for "MNQ Catering y Evento") is anchored to a broken `url` field.
|
||||
- **Status:** Already fixed in repo (`astro.config.mjs: site: 'https://www.mnqeventos.es'`, `MainLayout.astro` line 33/98, `SiteFooter.astro` line 22, `contacto.astro` line 108) but **not yet deployed**.
|
||||
- **Recommendation:** Deploy the pending fix immediately. This is the single highest-impact, already-solved change — it just needs to ship. Effort: none (code complete), deploy only.
|
||||
|
||||
### 2. `robots.txt` Sitemap directive points to a domain that does not exist — HIGH (live defect, fix pending deploy)
|
||||
- **Severity:** High
|
||||
- **Evidence (live):**
|
||||
```
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
Sitemap: https://www.mnqcatering.com/sitemap-index.xml
|
||||
```
|
||||
Verified: `curl -o /dev/null -w '%{http_code}' https://www.mnqcatering.com/sitemap-index.xml` → connection failure / `000` (domain unregistered). The correct, working sitemap is live and reachable at `https://www.mnqeventos.es/sitemap-index.xml` → HTTP 200, but nothing in the live `robots.txt` points there.
|
||||
- **Impact:** Crawlers that discover the sitemap only via `robots.txt` (a common pattern for AI/search crawlers doing full-site discovery) get a dead pointer and may fail to enumerate all indexable pages, slowing or preventing discovery of `/bodas-reales`, `/corporativo-business`, `/reuniones-familiares`, etc.
|
||||
- **Status:** Already fixed in repo `public/robots.txt` (now points to `https://www.mnqeventos.es/sitemap-index.xml`) — pending deploy.
|
||||
- **Recommendation:** Ships with fix #1's deploy. Effort: none, deploy only.
|
||||
|
||||
### 3. `robots.txt` is permissive for AI crawlers — GOOD (no action needed)
|
||||
- **Severity:** Positive finding
|
||||
- **Evidence:** `User-agent: * / Allow: /` — a single wildcard rule with no disallows. This implicitly allows GPTBot, OAI-SearchBot, ClaudeBot, PerplexityBot, CCBot, anthropic-ai, and cohere-ai; nothing is blocked.
|
||||
- **Recommendation:** No change needed. Optionally add explicit named-agent blocks for CCBot/anthropic-ai/cohere-ai only if the business wants to opt out of training-only crawling while keeping AI *search* visibility (GPTBot, OAI-SearchBot, ClaudeBot, PerplexityBot) — this is optional per the audit brief, not a defect.
|
||||
|
||||
### 4. `llms.txt` is present and live, but the deployed version is thinner and has the wrong canonical domain than the repo's current version — MEDIUM (live defect, fix pending deploy)
|
||||
- **Severity:** Medium
|
||||
- **Evidence — live, fetched 2026-09-15 (`https://www.mnqeventos.es/llms.txt`, HTTP 200):**
|
||||
```
|
||||
MNQ Catering y Evento
|
||||
|
||||
- Canonical domain: https://www.mnqcatering.com
|
||||
- Business type: Premium catering service
|
||||
- Services: Bodas, eventos corporativos, reuniones familiares
|
||||
- Primary contact channels: WhatsApp (+34 678 17 15 13), phone (+34 678 17 15 13)
|
||||
- Key pages: /, /nosotros, /bodas-reales, /corporativo-business, /reuniones-familiares, /contacto
|
||||
```
|
||||
- **Repo's current `public/llms.txt` (committed, not yet deployed)** adds two lines the live version is missing:
|
||||
```
|
||||
- Address: Calle Camino Vivero, 6, 29014, Málaga, España
|
||||
- Area served: Málaga (city) and Provincia de Málaga
|
||||
```
|
||||
and correctly says `Canonical domain: https://www.mnqeventos.es`.
|
||||
- **Format note (applies to both live and repo versions):** Neither follows the llms.txt spec convention closely — no `# Title` H1, no `>` summary blockquote, no markdown-style `[Key Page](full-url): description` links (currently plain relative paths in a flat bullet list). This is a minor quality gap versus the informal but now-common llms.txt convention; it doesn't block parsing but is a missed opportunity for AI agents that expect the structured format.
|
||||
- **Recommendation:** Deploy the repo's current version immediately (fixes domain + adds address/area-served — real local-entity signals). Separately, reformat to standard llms.txt structure (H1 title, one-line summary blockquote, markdown links per key page with a short description each) for better parseability by LLM-based agents. Effort: Low (content already exists, just needs reformatting).
|
||||
|
||||
### 5. No FAQ-style content on the homepage; direct-answer content is confined to `/contacto` — MEDIUM
|
||||
- **Severity:** Medium
|
||||
- **Evidence:** `/contacto` carries a genuine `FAQPage` JSON-LD block with 3 Q&A pairs (e.g. "¿Qué datos conviene enviar para pedir una propuesta?" → "Fecha estimada, lugar del evento, número aproximado de asistentes y el tipo de servicio que necesita.") and matching visible question-format subheadings ("Qué necesitamos para orientarle", "Cómo planteamos el presupuesto"). This is a genuine AEO positive — but it exists only on one page. The homepage, `/nosotros`, and the three service pages (not fetched this pass, but referenced identically in nav) use marketing prose headings ("Servicio planificado según ubicación, formato y ritmo del evento") rather than question-based H2/H3s, and no passage on the homepage is a self-contained 134–167-word answer block — most are 1–3 short sentences of brand copy.
|
||||
- **Recommendation:** Extend the `FAQPage` + question-based-heading pattern already proven on `/contacto` to the service pages (bodas, corporativo, reuniones-familiares) and `/nosotros`, each with 3–5 self-contained Q&A pairs in the 40–80 word range (FAQ answers don't need to hit the full 134–167-word "article passage" target, but should stay self-contained and specific). Effort: Medium — content writing + schema markup, reusing the existing `/contacto` pattern as a template.
|
||||
|
||||
### 6. No brand/entity linking signals (no `sameAs`, no social profile links anywhere on-site) — MEDIUM-HIGH
|
||||
- **Severity:** Medium-High
|
||||
- **Evidence:** Checked `/`, `/contacto`, `/nosotros` for LinkedIn, Instagram, Facebook, X/Twitter, YouTube, TikTok links — zero matches. The `CateringService` JSON-LD has no `sameAs` array. No mentions of Wikipedia, Reddit, or YouTube presence found on-site (external presence not independently verified this pass — no search/social-listening tool was used).
|
||||
- **Impact:** Per the brand-mention correlation data, YouTube mentions (~0.737) and Reddit presence are the strongest predictors of AI citation; Wikipedia entity presence is also high-value. Zero on-site `sameAs`/social signals means AI systems building an entity profile for "MNQ Catering y Evento" have nothing to cross-reference beyond the domain itself.
|
||||
- **Recommendation:** (a) Add a `sameAs` array to the `CateringService` JSON-LD linking to whatever active social profiles exist (Instagram/Facebook are typical for catering businesses even without LinkedIn/YouTube). (b) If no social profiles exist yet, creating even a modest Instagram/YouTube presence (venue photos, event highlight reels) would meaningfully move the needle given YouTube's outsized correlation with AI citation. Effort: Low for `sameAs` markup (if profiles exist), Medium-High for building new social presence from scratch.
|
||||
|
||||
### 7. No address / local-entity structured data live (fix pending deploy) — MEDIUM
|
||||
- **Severity:** Medium
|
||||
- **Evidence:** Live JSON-LD `CateringService` on `/`, `/contacto`, `/nosotros` has no `address`, `areaServed`, or `geo` fields — only `name`, `url` (wrong domain), `description`, `telephone`, `serviceType`, `contactPoint`. No address text appears anywhere in the visible page copy either. The repo's current `llms.txt` (undeployed) already has the address/area-served text; it's unclear from this pass whether the `CateringService` JSON-LD itself is being updated with structured `address`/`areaServed`/`geo` fields, or whether the fix is llms.txt-only.
|
||||
- **Recommendation:** Once the domain fix deploys, separately confirm (or add) structured `address` and `areaServed` fields inside the `CateringService` JSON-LD itself (not just llms.txt prose) — this is what powers Google's local entity panel and is a stronger machine-readable signal than plain text. Effort: Low, since the address text is already sourced (`Calle Camino Vivero, 6, 29014, Málaga, España`).
|
||||
|
||||
### 8. Server-side rendering confirmed — GOOD (no action needed)
|
||||
- **Severity:** Positive finding
|
||||
- **Evidence:** `render_page.py --mode auto` on `https://www.mnqeventos.es/` returned `"is_spa": false`, `"mode_used": "raw"` — the raw HTTP response already contains the full rendered page (hero copy, service cards, testimonials, footer, all JSON-LD) with no client-side hydration gap. This matches the Astro `output: 'server'` architecture confirmed in `astro.config.mjs`.
|
||||
- **Recommendation:** No change needed. This is the single strongest technical-accessibility asset the site has — every AI crawler sees the same content a browser does, with zero JS-execution risk.
|
||||
|
||||
### 9. Descriptive image alt text present — GOOD (partial credit)
|
||||
- **Severity:** Positive finding, with a gap
|
||||
- **Evidence:** Homepage images carry specific, descriptive `alt` text (e.g. `"MNQ Catering — salón de bodas premium"`, `"Vieiras con azafrán"`, `"Bartender de coctelería"`) rather than generic/empty alts. However, there is no video content anywhere observed, and no image captions or surrounding text that would let an AI engine extract a citable fact from an image (e.g. no recipe steps, no menu item descriptions tied to photos).
|
||||
- **Recommendation:** Keep the alt-text discipline for new images. Consider short video content (event highlight reels) for the Multi-Modal dimension, given video/YouTube's strong correlation with AI citation.
|
||||
|
||||
## GEO Health Score
|
||||
|
||||
**Overall: 46 / 100**
|
||||
|
||||
| Dimension | Weight | Score (0–100) | Weighted | Rationale |
|
||||
|---|---|---|---|---|
|
||||
| Citability | 25% | 50 | 12.5 | Solid FAQ schema + Q&A headings on `/contacto`; homepage/other pages are marketing prose, not self-contained answer blocks |
|
||||
| Structural Readability | 20% | 55 | 11.0 | Clean heading hierarchy; question-format H2/H3 only on `/contacto`, not site-wide |
|
||||
| Multi-Modal Content | 15% | 35 | 5.25 | Good descriptive alt text; no video, no YouTube presence, no image-adjacent citable facts |
|
||||
| Authority & Brand Signals | 20% | 30 | 6.0 | No `sameAs`/social links, no visible address/local-entity data live, wrong canonical/entity `url` currently live |
|
||||
| Technical Accessibility | 20% | 55 | 11.0 | SSR confirmed, permissive robots.txt, fast HTTP/2 — undercut by broken canonical, broken sitemap pointer, broken og:image |
|
||||
|
||||
**If the already-committed domain fix (commits `a6692b7` and related) is deployed as-is**, Technical Accessibility and Authority & Brand Signals would both improve materially (est. +15–20 points combined toward Technical, +5–10 toward Authority once address data ships in structured form), pushing the overall score into the high-50s/low-60s without any new work — that deploy is by far the highest-leverage action available right now.
|
||||
|
||||
## AI Crawler Access Status (robots.txt, live)
|
||||
|
||||
| Crawler | Status |
|
||||
|---|---|
|
||||
| GPTBot | Allowed (wildcard `Allow: /`) |
|
||||
| OAI-SearchBot | Allowed (wildcard `Allow: /`) |
|
||||
| ClaudeBot | Allowed (wildcard `Allow: /`) |
|
||||
| PerplexityBot | Allowed (wildcard `Allow: /`) |
|
||||
| CCBot | Allowed (wildcard `Allow: /`) — no opt-out currently configured |
|
||||
| anthropic-ai | Allowed (wildcard `Allow: /`) — no opt-out currently configured |
|
||||
| cohere-ai | Allowed (wildcard `Allow: /`) — no opt-out currently configured |
|
||||
|
||||
Note: `robots.txt` itself is reachable, but its `Sitemap:` directive is broken (see Finding #2), which can hinder full-site discovery regardless of the permissive `Allow` rule.
|
||||
|
||||
## llms.txt Status
|
||||
|
||||
**Present, HTTP 200, but stale/incomplete relative to the repo's current (undeployed) version.**
|
||||
- Live: 5 bullet lines, wrong canonical domain (`mnqcatering.com`), no address/area-served.
|
||||
- Repo (pending deploy): 7 bullet lines, correct canonical domain, includes address and area served.
|
||||
- Neither version follows the full llms.txt markdown convention (H1 + summary + linked key pages) — see Finding #4.
|
||||
|
||||
## Brand Mention Analysis
|
||||
|
||||
Not independently verified via external search/social-listening tools this pass (none available in this environment/session). On-site evidence only: **zero** `sameAs` links, **zero** social profile links (Instagram/Facebook/LinkedIn/YouTube/TikTok/X) found in the HTML of `/`, `/contacto`, `/nosotros`. This is a gap regardless of what external presence may exist, since AI entity-resolution systems weight *on-site* declared links (`sameAs`) heavily as a disambiguation signal. Recommend a follow-up pass with live search/social tools (or DataForSEO's `ai_opt_llm_ment_search`, if that MCP integration is enabled for this environment — it was not available this session) to check actual Wikipedia/Reddit/YouTube/LinkedIn mention volume before investing in new social presence.
|
||||
|
||||
## Platform-Specific Scores (qualitative estimate — no live scraping tool available this session)
|
||||
|
||||
| Platform | Estimated readiness | Basis |
|
||||
|---|---|---|
|
||||
| Google AI Overviews | Low-Medium | FAQPage schema present (favorable), but broken canonical/entity URL currently live undercuts trust signals |
|
||||
| ChatGPT (browsing/search) | Low | No `llms.txt` linking convention, weak brand-mention surface, broken og:image would fail citation-card rendering |
|
||||
| Perplexity | Low-Medium | Permissive robots.txt and clean SSR content favor crawlability; same entity/URL trust gap as above |
|
||||
| Bing Copilot | Low-Medium | Same reasoning as Google AIO (shares underlying Bing index signals) |
|
||||
|
||||
These are directional estimates based on on-site signals only, not live AI-response testing. If DataForSEO's `ai_optimization_chat_gpt_scraper` becomes available in a future session, re-run for actual observed visibility rather than inferred readiness.
|
||||
|
||||
## Top 5 Highest-Impact Changes
|
||||
|
||||
1. **Deploy the already-committed domain fix to production** — Effort: None (code complete) / Impact: High. Fixes canonical tags, `og:url`/`og:image`, JSON-LD `url`, footer link, and the broken `robots.txt` sitemap pointer in one deploy. This is the single highest-leverage action and requires zero new development.
|
||||
2. **Confirm the deployed `llms.txt` matches the repo's current version (with address + area-served) after deploy**, and reformat it to standard llms.txt markdown convention (H1, summary blockquote, linked key pages with descriptions) — Effort: Low.
|
||||
3. **Add structured `address`/`areaServed`/`geo` fields to the `CateringService` JSON-LD** (not just llms.txt prose) on `/`, `/contacto`, `/nosotros` — Effort: Low, address text already sourced.
|
||||
4. **Extend the `/contacto` FAQPage + question-heading pattern to the three service pages and `/nosotros`** — Effort: Medium, reuses an existing, working template.
|
||||
5. **Add a `sameAs` array to the JSON-LD entity linking real social profiles** (or build a minimal video/YouTube presence if none exist) — Effort: Low if profiles exist, Medium-High if starting from zero; highest-correlation lever per the brand-mention data once other fixes ship.
|
||||
|
||||
## What Already Works Well
|
||||
|
||||
- **Genuine SSR, no hydration gap.** `render_page.py` confirms the raw HTTP response is the full page — every AI crawler sees complete content with zero JS-execution dependency.
|
||||
- **Permissive `robots.txt`.** No AI crawler (GPTBot, OAI-SearchBot, ClaudeBot, PerplexityBot) is blocked; a single wildcard `Allow: /` covers everything.
|
||||
- **`llms.txt` exists and is live** (HTTP 200), and the repo's pending version is well-formed content-wise (business type, services, contact channels, key pages, address, area served) even if the markdown formatting could be tightened.
|
||||
- **Real `FAQPage` schema + question-based subheadings on `/contacto`**, with concise, accurate, self-contained answers — this is exactly the AEO pattern that should be replicated site-wide.
|
||||
- **Descriptive, specific image alt text** throughout the homepage gallery and service cards.
|
||||
- **Fast, modern hosting**: HTTP/2, `alt-svc: h3`, clean response headers, no redirect chains observed on the pages checked.
|
||||
- **The hard part (root-cause domain fix) is already done in code** — the team caught and fixed the canonical-domain issue proactively; it's purely a deployment gap away from resolving several of the findings above.
|
||||
@@ -0,0 +1,135 @@
|
||||
# Local SEO Audit — MNQ Catering y Evento
|
||||
|
||||
**Audit date:** 2026-09-15
|
||||
**Business:** MNQ Catering y Evento — premium catering, Local Service business type
|
||||
**Address (owner-confirmed):** Calle Camino Vivero 6, 29014 Málaga, España
|
||||
**Phone (owner-confirmed):** +34 678 17 15 13
|
||||
|
||||
## IMPORTANT — domain correction mid-audit
|
||||
|
||||
The audit was launched against `https://www.mnqcatering.com`, which does **not resolve** (confirmed NXDOMAIN via local resolver, and independently via the WebFetch tool's own DNS lookup — two different network paths both failed). The coordinator subsequently confirmed via WHOIS that **`mnqcatering.com` was never registered**, and that the actual canonical/live domain is **`https://www.mnqeventos.es`**.
|
||||
|
||||
All findings below marked **LIVE** were verified by directly fetching `https://www.mnqeventos.es` (home, `/contacto`, `/privacidad`, `/cookies`) just now. Findings marked **REPO** come from reading the Astro source in this working copy (`/home/manuel/Documentos/Proyectos/MNQ Catering y Evento`), which per the coordinator is mid-fix this session but **not yet deployed**.
|
||||
|
||||
**Do not conflate the two.** The repo checkout I read still has `CANONICAL_HOST = 'www.mnqcatering.com'` in `src/middleware.ts` and `@type: 'CateringService'` in `src/layouts/MainLayout.astro` (verified via `git diff` — no uncommitted changes to these files in this checkout), so whatever fix the coordinator describes (domain corrected, schema `@type` → `CateringBusiness`) exists in a different session/worktree, not in the files I inspected. I did not verify that fixed state directly — report it as coordinator-asserted, unconfirmed by me.
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
The live site (`www.mnqeventos.es`) has a **severe, site-wide NAP and canonicalization problem** that is worse than what the local repo checkout shows: every page checked has **zero occurrences of the street address, postal code, or "Málaga"** anywhere in the HTML (visible or structured), and every canonical tag, `og:url`, and JSON-LD `url` property on every page **self-declares the domain as `www.mnqcatering.com`** — a domain that is not registered. The phone number is present and consistent. The business name has a real plural/singular inconsistency baked into the site-wide header/footer logo `alt`/`aria-label` text. The only structured data on the live site is a single `CateringService` block (not a valid schema.org type) with no address, no areaServed, no geo.
|
||||
|
||||
The local repo checkout is meaningfully further along (has address in footer/contacto, has `PostalAddress` + `areaServed` in schema) but still carries the invalid `CateringService` type and the same name inconsistency, and still points at `mnqcatering.com` in its middleware/config as of this checkout.
|
||||
|
||||
GBP is not yet created — that's an acknowledged, expected gap given where this project is in its lifecycle, not scored as a site defect per se, but it does mean there is currently zero external signal reinforcing the business entity, which makes getting the on-site canonical/NAP layer exactly right more urgent, not less.
|
||||
|
||||
---
|
||||
|
||||
## Findings
|
||||
|
||||
### CRITICAL
|
||||
|
||||
**1. [LIVE] Canonical tag, `og:url`, and JSON-LD `url` all point to an unregistered domain (`mnqcatering.com`), not the live domain (`mnqeventos.es`)**
|
||||
Evidence, fetched from `www.mnqeventos.es` just now:
|
||||
- `<link rel="canonical" href="https://www.mnqcatering.com/">` (present on `/`, `/contacto`, and presumably every page)
|
||||
- `<meta property="og:url" content="https://www.mnqcatering.com/">`
|
||||
- JSON-LD: `"url":"https://www.mnqcatering.com"`
|
||||
- Footer/nav links: `href="https://www.mnqcatering.com/"` and `href="https://www.mnqcatering.com"`
|
||||
This means the site's self-declared canonical identity — the single most important machine-readable signal for Google to associate the site with a future GBP listing — points at a domain that does not exist. This will confuse indexing (Google may treat the declared canonical as authoritative and effectively deprioritize/ignore the real, resolvable `mnqeventos.es` URLs) and would actively sabotage GBP verification/association once a listing is created, since the GBP website field must match a URL the site itself claims as canonical.
|
||||
**Recommendation:** Fix `CANONICAL_HOST` (and every hardcoded `mnqcatering.com` reference — `astro.config.mjs` `site:`, `MainLayout.astro` fallback URL, JSON-LD `url`, footer/contacto links, `robots.txt` sitemap line, `llms.txt`) to `www.mnqeventos.es` and **deploy**. This is the single highest-priority fix on the entire site.
|
||||
|
||||
**2. [LIVE] No address anywhere on the live site — visible or structured**
|
||||
Fetched and grepped `/`, `/contacto`, `/privacidad`, `/cookies`: "Camino Vivero", "29014", and "Málaga" each return **0 matches** across all four pages. The phone number is present (`+34 678 17 15 13`, 2–3 occurrences per page) but the address is completely absent, including from `/contacto`, which is where a user (and Google) would most expect to find it. The live JSON-LD has no `address` property and no `areaServed` at all — just `name`, `url` (wrong domain), `description`, `telephone`, `serviceType`, `contactPoint`.
|
||||
**Recommendation:** Deploy whatever fixes address rendering on the live site (the repo checkout I read does have the address in `SiteFooter.astro` and `contacto.astro`, and `PostalAddress` + `areaServed` in the schema — so this may already be solved in a newer, undeployed commit). Until deployed, Google has literally nothing to anchor a "Málaga" local entity to on the live site.
|
||||
|
||||
**3. [REPO, and confirmed live] `@type: "CateringService"` is not a valid schema.org type**
|
||||
Verified directly against schema.org's own vocabulary (fetched `schema.org/CateringService` → HTTP 404; fetched `schema.org/FoodEstablishment` subtype list and the full current JSON-LD vocabulary dump → no type or label containing "Catering" or "Caterer" exists anywhere in schema.org). This type is live on `www.mnqeventos.es` right now (confirmed in the fetched JSON-LD) and is also what's in the repo checkout I read. An unrecognized `@type` means Google's structured-data parser has no LocalBusiness subtype to key off of — no rich-result eligibility, weaker entity understanding, and no reinforcement for a future GBP category match (primary GBP category is the #1 local ranking factor per Whitespark 2026, and a wrong/missing category is the #1 negative factor).
|
||||
Note: the coordinator states this was corrected to `CateringBusiness` in a fix not yet deployed. I could not verify `CateringBusiness` either way — it is **also not a standard schema.org type** as far as I could confirm (schema.org has no dedicated caterer subtype at all). The safest documented options are plain `LocalBusiness` (generic, always valid) or `FoodEstablishment` if the business wants to lean on the food-service branch, with `additionalType`/`serviceType` used to convey "catering." Whoever owns that fix should double-check `CateringBusiness` against schema.org before deploying — if it doesn't validate in Google's Rich Results Test either, it's the same problem under a new name.
|
||||
|
||||
### HIGH
|
||||
|
||||
**4. [LIVE + REPO] Business name inconsistency: "MNQ Catering y Evento" vs "MNQ Catering y Eventos"**
|
||||
Confirmed on both the live site and in the repo source. The site-wide header logo (`SiteHeader.astro`) and footer logo (`SiteFooter.astro`) both use the **plural** "MNQ Catering y Eventos" in `alt` text and `aria-label` — this appears on literally every page since header/footer are global. Everywhere else — page `<title>` tags, `og:site_name`, the JSON-LD `name` property, the footer copyright line, `llms.txt` — uses the **singular** "MNQ Catering y Evento". Live grep counts on the homepage: plural form 4 occurrences (all header/footer), singular form 12 occurrences (title, meta, schema, copyright). Business Name is the "N" in NAP; an exact-match name is what Google uses to tie a website to a GBP listing. Two names in active use sitewide is a real, avoidable inconsistency.
|
||||
**Recommendation:** Pick one (the schema/title form, "MNQ Catering y Evento", is the one that should become the GBP business name — it's already dominant) and fix the two `alt`/`aria-label` occurrences in `SiteHeader.astro` and `SiteFooter.astro` to match.
|
||||
|
||||
**5. [LIVE] No `areaServed` or `geo` in live schema (repo has `areaServed`, still no `geo`)**
|
||||
Live JSON-LD has neither. The repo checkout's `MainLayout.astro` does include `areaServed` (City: Málaga + AdministrativeArea: Provincia de Málaga), which is good and correctly scoped, but geo coordinates are intentionally omitted with a code comment ("Geo coordinates not confirmed — left out") — a defensible conservative choice given they're not owner-confirmed, but it means the schema is missing a recommended property (5-decimal-precision `geo` is explicitly called out as a "recommended" LocalBusiness property). Once the physical address is confirmed usable for GBP verification, geocoding it (e.g., via a geocoding API, matched against what will eventually be entered in GBP) and adding `geo` should follow, not precede, GBP setup — don't guess coordinates.
|
||||
|
||||
**6. No `openingHoursSpecification` anywhere (live or repo)**
|
||||
No opening hours are declared in schema, and I found no visible hours copy on the pages read. For an event-catering business without walk-in hours this is lower-urgency than for a storefront, but if there are defined business hours for phone/WhatsApp inquiries, adding `openingHoursSpecification` (or explicitly modeling this as a by-appointment/SAB business without standard hours) helps completeness.
|
||||
|
||||
**7. No `Service` schema per offering (Bodas, Corporativo, Reuniones Familiares)**
|
||||
`bodas-reales.astro`, `corporativo-business.astro`, and `reuniones-familiares.astro` are genuinely dedicated, differentiated service pages (good — per Whitespark 2026 this is the #1 local organic ranking factor and #2 AI-visibility factor) but only carry `FAQPage` schema, not `Service` entities with their own `areaServed`/`provider` linking back to the business. Adding `Service` markup per offering (name, `areaServed`, `provider: {"@id": "...#business"}`) would strengthen this further.
|
||||
|
||||
### MEDIUM
|
||||
|
||||
**8. `/privacidad` and `/cookies` omit the physical address (repo state)**
|
||||
In the repo checkout, `privacidad.astro`'s "Responsable del Tratamiento" section lists only phone and website, no street address — same for `/cookies`. Not a contradiction (nothing wrong is stated), but it's a missed reinforcement opportunity on two more indexable-by-Google pages, and for a Spain-based business, LSSI/RGPD "Responsable del Tratamiento" sections conventionally do include the operating address. Tied to finding below.
|
||||
|
||||
**9. `/aviso-legal` is explicitly provisional, missing NIF/CIF and registered address**
|
||||
The page self-declares ("Aviso provisional pendiente de completar") that legal identity (NIF/CIF, domicilio social) is not yet finalized. This is a legal-compliance gap more than a pure local-SEO one, but it also means there is currently no page on the site carrying the formal registered-entity address that would need to match GBP's business-verification documents later. Flagging so it's on the radar before GBP verification is attempted (GBP verification frequently requires documents matching the on-site legal entity/address).
|
||||
|
||||
**10. `astro.config.mjs` sitemap filter excludes `/cookies` and `/privacidad` but not `/aviso-legal`**
|
||||
Minor technical-SEO note found while reading: `/aviso-legal` has `robots: noindex,follow` but is not excluded from the sitemap generator, so it will appear in `sitemap-index.xml` despite being noindexed — inconsistent with how `/cookies` and `/privacidad` are handled. Not a local-SEO scoring factor but cheap to fix alongside the other config changes.
|
||||
|
||||
**11. No Tier-1 citation presence verifiable**
|
||||
No outbound links to Yelp, TripAdvisor, Google Maps place pages, or Spain-relevant directories (e.g., Páginas Amarillas, Bodas.net, Zankyou — all commonly used by Spanish wedding/catering vendors) exist anywhere in the codebase, and none could be verified live without paid tooling. Given catering/weddings is the vertical, Bodas.net and Zankyou are more relevant citation targets for this business than the generic Yelp/BBB pair — worth prioritizing once GBP exists.
|
||||
|
||||
### LOW
|
||||
|
||||
**12. Locale signals are solid** — `<html lang="es-ES">` and `og:locale: es_ES` are consistent on both live and repo. No action needed, listed here only because it was explicitly in scope to check and it passes.
|
||||
|
||||
---
|
||||
|
||||
## Local SEO Score: 22 / 100 (live site) — repo checkout, if deployed as-is, would score materially higher (~34/100), see caveat
|
||||
|
||||
This score reflects the **live site** (`www.mnqeventos.es`), since that's what Google is actually crawling today. The repo checkout has a partial fix in progress (address present, `areaServed` present) but still ships the invalid schema `@type` and the wrong canonical domain in every config file I read, so it should not be treated as "done" either.
|
||||
|
||||
| Dimension | Weight | Live score | Weighted |
|
||||
|---|---|---|---|
|
||||
| GBP Signals | 25% | 5/100 | 1.25 |
|
||||
| Reviews & Reputation | 20% | 30/100 | 6.0 |
|
||||
| Local On-Page SEO | 20% | 30/100 | 6.0 |
|
||||
| NAP Consistency & Citations | 15% | 15/100 | 2.25 |
|
||||
| Local Schema Markup | 10% | 15/100 | 1.5 |
|
||||
| Local Link & Authority Signals | 10% | 15/100 | 1.5 |
|
||||
| **Total** | | | **~22 / 100** |
|
||||
|
||||
Notes on the two lowest-context dimensions:
|
||||
- **GBP Signals (5/100):** No Maps embed, no place reference, no review widget, no GBP-sourced photos anywhere — expected and acknowledged, since GBP hasn't been created yet. Scored low because the checklist asks for on-page evidence of GBP integration, and there is none, not because the business did anything wrong by not having GBP yet.
|
||||
- **Reviews & Reputation (30/100):** Hardcoded testimonials with decorative 5-star UI exist on the homepage and `/bodas-reales` (repo-confirmed; not independently re-verified live within the turn budget), but none are marked up as `Review`/`aggregateRating` schema, none are sourced from or attributable to a live review platform, and there is no review velocity data (no GBP yet). Counted as a trust signal, not a verifiable review asset.
|
||||
|
||||
---
|
||||
|
||||
## What already works well
|
||||
|
||||
- **Phone number consistency is genuinely solid**, live and in repo: `+34 678 17 15 13` (display) / `tel:+34678171513` (href) is identical across home, `/contacto`, `/privacidad`, `/cookies` on the live site.
|
||||
- **Dedicated service pages exist** for the three core offerings (Bodas, Corporativo, Reuniones Familiares) rather than one generic services page — this is directionally correct per current local-SEO best practice, it just needs schema to match.
|
||||
- **Locale/language signals are correct and consistent** (`es-ES`, `es_ES`) across the whole site.
|
||||
- **The repo checkout is already ahead of production** on address and `areaServed` in schema — once deployed (with the domain and `@type` also fixed), several of the findings above resolve at once.
|
||||
- **The catering business model (base address + event-location service) is a legitimate hybrid**, and the `areaServed` approach in the repo (City: Málaga + AdministrativeArea: Provincia de Málaga) is appropriately scoped rather than overreaching.
|
||||
|
||||
---
|
||||
|
||||
## Top 10 Prioritized Actions
|
||||
|
||||
1. **[Critical]** Fix every hardcoded reference to `mnqcatering.com` (canonical tag, `og:url`, JSON-LD `url`, footer/contacto links, `astro.config.mjs` `site`, `robots.txt`, `llms.txt`) to `www.mnqeventos.es`, and **deploy to production**. Verify live afterward — do not assume the repo fix is sufficient until it's actually served.
|
||||
2. **[Critical]** Deploy the address/`PostalAddress`/`areaServed` fix that's in the repo checkout but not live — currently the live site has zero address signal anywhere.
|
||||
3. **[Critical]** Replace the invalid `@type: "CateringService"` with a schema.org-valid type. Verify whatever replacement is chosen (including the coordinator-mentioned `CateringBusiness`) against Google's Rich Results Test before considering this closed — don't trade one invalid type for another.
|
||||
4. **[High]** Fix the plural/singular brand name inconsistency in `SiteHeader.astro` and `SiteFooter.astro` (`alt`/`aria-label`) to match the singular form used everywhere else.
|
||||
5. **[High]** Add `Service` schema (with `areaServed` and `provider` back-reference) to each of the three offering pages.
|
||||
6. **[High]** Once the primary fixes are deployed and stable, create and verify the Google Business Profile using the exact corrected name/address/phone/URL — this is the acknowledged next external step, not a site defect.
|
||||
7. **[Medium]** Add the operating address to `/privacidad`'s "Responsable del Tratamiento" section and to `/cookies` for reinforcement.
|
||||
8. **[Medium]** Finalize `/aviso-legal` with NIF/CIF and registered address before attempting GBP verification, since verification documents typically need to match.
|
||||
9. **[Medium]** Exclude `/aviso-legal` from the sitemap (or remove its `noindex`) to make sitemap/robots directives internally consistent.
|
||||
10. **[Low]** Once GBP exists, pursue vertical-relevant citations (Bodas.net, Zankyou, Google Maps) rather than generic Yelp/BBB, which are less relevant to a Spain-based wedding/events caterer.
|
||||
|
||||
---
|
||||
|
||||
## Limitations disclaimer
|
||||
|
||||
- **Domain confusion mid-audit**: the audit began against `mnqcatering.com`, confirmed non-resolving via two independent DNS paths (local resolver NXDOMAIN, and WebFetch's own `getaddrinfo ENOTFOUND`), then was redirected mid-task to the real live domain `www.mnqeventos.es` per coordinator/WHOIS confirmation. All "LIVE" findings above come from directly fetching `www.mnqeventos.es` in this session; I did not have time within the turn budget to re-check every page (e.g., `/nosotros`, `/bodas-reales`, `/corporativo-business`, `/reuniones-familiares` were read from the repo but not re-fetched live) — treat those as REPO-sourced only.
|
||||
- **Could not verify the coordinator-asserted "fixed" state** (domain + `CateringBusiness` type) directly — no uncommitted diff for those files existed in this checkout (`git diff` on `src/middleware.ts`, `src/layouts/MainLayout.astro`, `astro.config.mjs` was empty), so that fix lives elsewhere (different session/worktree) and is reported as unconfirmed, not verified.
|
||||
- **No GBP, no paid tools**: GBP doesn't exist yet, so review velocity, GBP category, Maps pack position, and DataForSEO/live-SERP checks are all inherently unavailable — not a site defect, just out of scope for on-page analysis.
|
||||
- **Citation presence** (Yelp/BBB/Bodas.net/etc.) was checked only by absence of outbound links in source/HTML, not via direct site: searches or crawls of those third-party platforms — a false negative is possible if the business is listed there without the live site linking to it.
|
||||
- **Turn-limited pass**: given the mid-task domain correction and remaining turn budget, I prioritized the highest-signal checks (canonical/NAP/schema across home + 3 legal/contact pages) over exhaustively re-fetching every service page live.
|
||||
@@ -0,0 +1,112 @@
|
||||
# Performance / Core Web Vitals Audit
|
||||
|
||||
**Important correction during this audit:** the domain named in the audit brief, `mnqcatering.com`, does not resolve on the public internet (confirmed via DoH lookups and direct curl from an unrestricted network — NXDOMAIN). The coordinator confirmed it was never registered. The actual live/canonical site is **https://www.mnqeventos.es**. All live measurements below target that domain. Note the codebase still has `mnqcatering.com` hard-coded as `astro.config.mjs`'s `site` value, which propagates into canonical tags, sitemap URLs, and the umami analytics `data-domains` attribute on every page — a correctness bug adjacent to this audit's scope (SEO/analytics), flagged here because it was discovered while investigating why "live" checks were failing.
|
||||
|
||||
## Tooling used
|
||||
|
||||
- **PageSpeed Insights / CrUX API**: unavailable. No Google API key is configured in this environment (`~/.config/claude-seo/google-api.json` absent, `GOOGLE_API_KEY` unset — confirmed via `google_auth.py --check`, tier `-1`). No field data (CrUX) or PSI-hosted lab data could be retrieved.
|
||||
- **Lighthouse 13.4.1 (via `npx lighthouse`)**: available and used directly against the live site. Mobile form factor, simulated throttling (Lighthouse's standard mobile lab preset). This is **lab data from a single run**, not field data — treat as directionally reliable, not as the CrUX 75th-percentile figure Google actually scores against.
|
||||
- **curl**: used for real TTFB/timing and raw-header checks.
|
||||
- Static source inspection of the Astro codebase (`src/pages`, `src/styles/animations.css`, built HTML) to explain *why* the lab numbers look the way they do.
|
||||
|
||||
Pages tested: homepage (`/`), `/bodas-reales`, `/corporativo-business`. A `/corporativo-business` Lighthouse run was queued but not completed before the turn budget ran out — that page's numbers below are from HTML/resource inspection only, not a Lighthouse run.
|
||||
|
||||
## Summary
|
||||
|
||||
Core Web Vitals are **mixed**: CLS and INP-proxy (TBT) are in great shape (image dimensions and lazy-loading from the prior remediation round are working correctly). LCP is the weak point on both pages measured with Lighthouse — homepage 3.4s (needs improvement), `/bodas-reales` 4.3s (poor) — driven mostly by **element render delay**, not by slow image download. Root causes found: a render-blocking, half-unused CSS file loaded on every page, a duplicated/blocking Google Fonts `<link>` that undermines the earlier preload+swap fix, a CSS `animation-delay` applied directly to the LCP heading, no text compression on the HTML document, and very large below-the-fold images (up to 246 KB each) that inflate total page weight even though they aren't the LCP element.
|
||||
|
||||
## Measured Core Web Vitals (Lighthouse lab data, mobile, simulated throttling)
|
||||
|
||||
| Page | Performance score | LCP | CLS | TBT (INP proxy) | FCP | Speed Index |
|
||||
|---|---|---|---|---|---|---|
|
||||
| `/` (home) | 84/100 | **3.4 s** (needs improvement) | 0 (good) | 0 ms (good) | 2.9 s | 4.4 s |
|
||||
| `/bodas-reales` | 78/100 | **4.3 s** (poor) | 0 (good) | 0 ms (good) | 3.0 s | 4.5 s |
|
||||
| `/corporativo-business` | not run (turn budget) | not measured | not measured | not measured | not measured | not measured |
|
||||
|
||||
Real TTFB measured via curl (not Lighthouse, actual network round-trip from this environment):
|
||||
|
||||
| Page | TTFB | Total transfer time | HTTP version |
|
||||
|---|---|---|---|
|
||||
| `/` | 186 ms | 229 ms | HTTP/2 |
|
||||
| `/bodas-reales` | 180 ms | 222 ms | HTTP/2 |
|
||||
| `/corporativo-business` | 218 ms | 269 ms | HTTP/2 |
|
||||
|
||||
TTFB is good on all three (well under the ~600ms "poor" territory Lighthouse itself flags server response at 67-70ms in its own trace). Server response time is not the bottleneck.
|
||||
|
||||
## Findings
|
||||
|
||||
### 1. LCP element render delay dominates load time on home (2.18s of the 3.4s LCP) — Severity: High
|
||||
**Evidence:** Lighthouse's `lcp-breakdown-insight` on the homepage identifies the LCP element as the hero `<h1>` ("MNQ: donde la tradición se encuentra con la exclusividad"), not the hero image. Breakdown: TTFB 187ms, **element render delay 2,179ms**. The `<h1>` carries class `hero-enter-2`, and `src/styles/animations.css` defines:
|
||||
```
|
||||
.hero-enter-1 { animation-delay: 200ms; }
|
||||
.hero-enter-2 { animation-delay: 500ms; }
|
||||
.hero-enter-3 { animation-delay: 780ms; }
|
||||
.hero-enter-4 { animation-delay: 1050ms; }
|
||||
```
|
||||
The LCP text is intentionally held invisible (fade/slide-in) via `animation-delay` before it can paint, on top of the render-blocking CSS below. This single change is likely the highest-leverage LCP fix available.
|
||||
**Recommendation:** Remove the entrance animation (or at least `animation-delay`) from whichever element Lighthouse marks as LCP — do not delay-hide the first paint of hero text/heading. Decorative stagger animations should be reserved for secondary/below-the-fold content, or use a technique that doesn't hide the LCP candidate from the paint timeline (e.g. animate `transform`/opacity only after `DOMContentLoaded` on non-LCP siblings).
|
||||
|
||||
### 2. Render-blocking, half-unused CSS file loaded on every page — Severity: High
|
||||
**Evidence:** Every page checked (`/`, `/bodas-reales`, `/corporativo-business`) loads `<link rel="stylesheet" href="/_astro/aviso-legal.xNce9nS9.css">` unconditionally. Lighthouse: 24.4 KB transfer, 450ms added to the render-blocking critical path on the homepage, and `unused-css-rules` reports **12 KB (roughly half the file) unused** on pages that aren't `/aviso-legal`. `network-dependency-tree-insight` shows this CSS file as the longest critical-path dependency (336ms chain). This looks like an Astro CSS-bundling side effect (the legal-notice page's styles are being emitted into a chunk that ends up referenced from the global layout) rather than intentional per-page CSS splitting.
|
||||
**Recommendation:** Investigate why `aviso-legal`-scoped CSS is bundled into the shared/global stylesheet reference instead of being page-scoped. Astro normally scopes component styles per page automatically; this typically means a shared import (e.g. a global layout or `<style is:global>` block) is pulling in styles that belong only to the legal-notice page. Splitting it out removes ~450ms from the critical path and ~12 KB of dead weight on every non-legal page.
|
||||
|
||||
### 3. Google Fonts stylesheet is loaded twice — once correctly deferred, once still blocking — Severity: Medium
|
||||
**Evidence:** In the built `<head>` on all three pages:
|
||||
```html
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link rel="preload" as="style" href="https://fonts.googleapis.com/css2?...&display=swap" onload="this.onload=null;this.rel='stylesheet'">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?...&display=swap">
|
||||
```
|
||||
The preload+swap pattern from the prior remediation round is present and correct — but it's immediately followed by a **second, plain `<link rel="stylesheet">` to the exact same URL**, which is a standard render-blocking stylesheet request. The preload/swap trick only helps if the *only* reference to that stylesheet is the deferred one; the duplicate blocking link re-introduces the exact render-blocking behavior the preload was meant to avoid (browsers will still block rendering on a `rel=stylesheet` link regardless of a sibling preload for the same resource).
|
||||
**Recommendation:** Remove the plain `<link rel="stylesheet">` duplicate. The correct pattern needs a `<noscript>` fallback for the no-JS case, not a second always-present blocking link:
|
||||
```html
|
||||
<link rel="preload" as="style" href="..." onload="this.onload=null;this.rel='stylesheet'">
|
||||
<noscript><link rel="stylesheet" href="..."></noscript>
|
||||
```
|
||||
This is a regression/incomplete application of the earlier font fix and should be quick to correct.
|
||||
|
||||
### 4. No text compression on the HTML document — Severity: Medium
|
||||
**Evidence:** `document-latency-insight` on the homepage: `usesCompression: false`, 16,780 uncompressed bytes, ~16 KB estimated savings. Confirmed independently: `curl -D -` response headers show no `Content-Encoding` header at all (no gzip/br). This is server/hosting config (Node/Astro `output: 'server'` via `@astrojs/node` standalone adapter, per `astro.config.mjs`), not a code-level fix in the page templates.
|
||||
**Recommendation:** Enable gzip or brotli compression at whatever sits in front of the Node adapter (reverse proxy/CDN — check the Dockerfile/nixpacks deployment path) or via Node-level compression middleware if nothing is in front of the app. Brotli preferred for text assets.
|
||||
|
||||
### 5. Large below-the-fold images inflate total page weight (not the LCP element, but still real) — Severity: Medium
|
||||
**Evidence:** `image-delivery-insight` on the homepage flags six images totaling ~606 KB of estimated savings, none of which is the LCP element:
|
||||
- Services-grid images: 246 KB, 222 KB, 218 KB (each ~70-80% wasted via better compression and serving at display size instead of native size — e.g. a 635×848 source shown at 723×485 or a 576×768 source shown at 870×485)
|
||||
- Header logo: 41.6 KB total, but only **~1.5 KB actually needed** — it's a 600×232 source image displayed at 93×36 (40.6 KB wasted on oversizing alone)
|
||||
- Hero background image itself: 75.9 KB, 45.5 KB potential savings from better compression (this one *is* near the LCP path but is not the identified LCP element on mobile)
|
||||
These are properly `loading="lazy"`/`decoding="async"` (confirmed in HTML — the prior lazy-loading fix is intact) so they don't block first paint, but they add real network/decode cost and count toward total page weight, which matters for INP on lower-end devices and for users who scroll quickly.
|
||||
**Recommendation:** Serve the header logo at its actual display size (93×36 → a ~2-4 KB asset, not 41 KB) or use `srcset`. Re-export the services-grid and gallery images at their real display dimensions with higher WebP/AVIF compression — 70-80% savings available with no visible quality loss per Lighthouse's compression estimate.
|
||||
|
||||
### 6. Forced reflow from inline script — Severity: Low
|
||||
**Evidence:** `forced-reflow-insight` flags 85ms of forced synchronous layout attributed to an inline `<script>` on the homepage (line 32 of the rendered HTML — one of the `type="module"` inline scripts). Not large enough to move TBT off 0ms in this lab run, but worth a look since forced reflows scale badly on low-end mobile CPUs (this lab run used simulated, not literal CPU, throttling).
|
||||
**Recommendation:** Identify the script reading a geometric property (`offsetWidth`, `getBoundingClientRect`, etc.) right after a DOM/class mutation and batch reads before writes, or move the read to `requestAnimationFrame`.
|
||||
|
||||
### 7. `/corporativo-business` not fully measured — Severity: N/A (coverage gap)
|
||||
**Evidence:** A live Lighthouse run for this page did not complete before the turn budget was reached. Static inspection of its built HTML shows the same render-blocking `aviso-legal.css`, the same duplicated Google Fonts link, and a hero image (`foto-corporativo.jpg`, JPG not WebP, unlike the homepage's generated hero) marked `loading="eager" fetchpriority="high"` with explicit `width`/`height` — dimensions and priority hints look correct, but its LCP timing and image weight were not independently measured.
|
||||
**Recommendation:** Re-run `npx lighthouse https://www.mnqeventos.es/corporativo-business --only-categories=performance --form-factor=mobile` to get real numbers before treating this page as verified. Given it shares the site-wide CSS/font issues above, expect it to score similarly to `/bodas-reales`, but the `foto-corporativo.jpg` being a plain JPG (not `.webp`) rather than passed through the same `generated/*.webp` image pipeline as other hero images is worth checking specifically — it may be the LCP element on this page.
|
||||
|
||||
## What already works well
|
||||
|
||||
- **Image dimensions and lazy-loading (prior remediation round confirmed intact):** every non-hero `<img>` on all three pages has explicit `width`/`height` attributes and `loading="lazy" decoding="async"`; hero images correctly use `loading="eager" fetchpriority="high"`. This is reflected in the measured **CLS = 0** on both pages tested — no layout-shift issues found.
|
||||
- **TBT = 0ms** on both measured pages — no long main-thread JavaScript tasks; scripts are `type="module"` (deferred by default) or `async defer` (the umami analytics tag). INP is very unlikely to be a problem based on this.
|
||||
- **Server response time is fast:** 67-70ms per Lighthouse's own trace, 180-220ms real TTFB via curl including full TLS handshake — server/hosting is not a bottleneck.
|
||||
- **HTTP/2 in use**, `preconnect` hints present for the Google Fonts origins, `font-display: swap` requested in the Fonts API URL — the *mechanism* of the prior font fix is correct, only the duplicate blocking link (finding 3) undermines it.
|
||||
- **No redirect chains** on the document request (`document-latency-insight`: `noRedirects: true`).
|
||||
|
||||
## Overall performance score
|
||||
|
||||
- **Homepage: 84/100** (Lighthouse mobile lab, measured)
|
||||
- **`/bodas-reales`: 78/100** (Lighthouse mobile lab, measured)
|
||||
- **`/corporativo-business`: not measured** — estimated in the high-70s/low-80s range based on shared template issues (CSS/font duplication) but this is an **estimate, not a measurement**, and should not be treated as verified.
|
||||
|
||||
These are single-run lab scores, not the CrUX field 75th-percentile Google actually uses for Core Web Vitals pass/fail — no field data was obtainable in this environment (no API credentials configured). If a Google Search Console / PageSpeed Insights API key becomes available, re-run `pagespeed_check.py` for field-data confirmation, especially since real-user LCP on a range of devices/networks could differ meaningfully from this single simulated-throttling lab run.
|
||||
|
||||
## Priority order for fixes
|
||||
|
||||
1. Remove `animation-delay` from the LCP heading (finding 1) — single highest-leverage change, likely worth ~0.5-2s of LCP.
|
||||
2. Fix the duplicate render-blocking Google Fonts `<link>` (finding 3) — quick, low-risk.
|
||||
3. Investigate/fix the `aviso-legal.css` bundling leak (finding 2) — removes 450ms render-blocking + 12KB dead weight sitewide.
|
||||
4. Enable HTML compression at the hosting/proxy layer (finding 4).
|
||||
5. Re-compress and right-size the flagged images (finding 5) — lower urgency since they're lazy-loaded and off the LCP path, but real savings for total page weight.
|
||||
6. Complete a Lighthouse run for `/corporativo-business` to close the coverage gap (finding 7).
|
||||
@@ -0,0 +1,161 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,87 @@
|
||||
# Sitemap Architecture Audit — mnqeventos.es (canonical live domain)
|
||||
|
||||
Date: 2026-09-15
|
||||
Scope: live sitemap at `https://www.mnqeventos.es/sitemap-index.xml`, `src/pages/` route set, `astro.config.mjs`, `src/middleware.ts`, `@astrojs/sitemap` output.
|
||||
|
||||
## Correction from earlier pass
|
||||
|
||||
An earlier pass of this audit targeted `https://www.mnqcatering.com/sitemap-index.xml`. That domain is confirmed **not registered** (DNS `NXDOMAIN` on two resolvers, WHOIS "no match" for `MNQCATERING.COM`) — this was not a sandbox artifact, it's genuinely unregistered. The actual canonical, live, registered production domain is **`https://www.mnqeventos.es/`**. This report re-audits against that domain, checked live.
|
||||
|
||||
## Summary — what's actually live right now
|
||||
|
||||
Live-fetched and verified directly (not from local build):
|
||||
|
||||
- `https://www.mnqeventos.es/sitemap-index.xml` → **HTTP 200**, valid XML, but its one `<sitemap><loc>` entry points to **`https://www.mnqcatering.com/sitemap-0.xml`** — i.e. the sitemap index is still wired to the old, unregistered domain.
|
||||
- Trying to actually fetch `https://www.mnqcatering.com/sitemap-0.xml` (the URL the live index points to) fails: `Could not resolve host`. **The referenced sub-sitemap is unreachable — Googlebot cannot follow the index to any URL list.**
|
||||
- `https://www.mnqeventos.es/sitemap-0.xml` (fetched directly, not via the index) does return HTTP 200 and 7 `<url>` entries, but **every `<loc>` inside it is still `https://www.mnqcatering.com/...`** — none of them use the live `mnqeventos.es` domain. Even a crawler that finds this file by luck gets a list of 7 dead URLs.
|
||||
- `https://www.mnqeventos.es/robots.txt` → live, `Sitemap:` line still points to `https://www.mnqcatering.com/sitemap-index.xml`.
|
||||
- `https://mnqeventos.es/` (bare apex, no `www`) did not respond in this environment (connect/timeout) — could not confirm apex→www redirect behavior live.
|
||||
|
||||
**Net effect: right now, in production, the sitemap infrastructure at the real domain is 100% non-functional.** The index file exists and is valid XML, but every path a crawler can take from it (via `<sitemap><loc>`, or via `robots.txt`, or by url-list entries once inside `sitemap-0.xml`) terminates at the unregistered `mnqcatering.com` domain.
|
||||
|
||||
## Codebase state vs. deployed state
|
||||
|
||||
The source code has already been fixed, but **the fix is not deployed**:
|
||||
|
||||
- `astro.config.mjs:6` → `site: 'https://www.mnqeventos.es'` (correct).
|
||||
- `src/middleware.ts:3` → `const CANONICAL_HOST = 'www.mnqeventos.es';`, with an explicit comment: *"www.mnqeventos.es is the real, registered, live production domain. mnqcatering.com was an ... do NOT set CANONICAL_HOST back to it..."*
|
||||
- This fix is **committed**: `git log` shows commit `a6692b7` — `fix(seo): correct canonical domain to www.mnqeventos.es` — at `2026-09-15 10:07:24 +0200` (today, this session).
|
||||
- A local `astro build` run against the current source produces a **correct** `dist/client/sitemap-0.xml` with all 7 `<loc>` entries on `https://www.mnqeventos.es/...`.
|
||||
- Conclusion: the commit is real and correct, but the running production deployment predates it and is serving a stale build. **This is a deploy/release gap, not a remaining code defect.**
|
||||
|
||||
## Findings
|
||||
|
||||
### 1. Live sitemap index points to a dead sub-sitemap on an unregistered domain
|
||||
- **Severity:** Critical
|
||||
- **Evidence:** live `https://www.mnqeventos.es/sitemap-index.xml` → `<loc>https://www.mnqcatering.com/sitemap-0.xml</loc>`; fetching that URL fails to resolve.
|
||||
- **Recommendation:** Deploy the already-committed fix (`a6692b7`) to production. Until deployed, Search Console will report the submitted sitemap as unreachable/errored, and no URLs from this site are discoverable via sitemap at all.
|
||||
|
||||
### 2. Live sub-sitemap URLs all reference the unregistered domain
|
||||
- **Severity:** Critical
|
||||
- **Evidence:** `https://www.mnqeventos.es/sitemap-0.xml` (live, HTTP 200) lists all 7 URLs as `https://www.mnqcatering.com/...`.
|
||||
- **Recommendation:** Same fix/deploy as above resolves this; the local rebuilt `dist/client/sitemap-0.xml` already generates correct `mnqeventos.es` URLs from current source.
|
||||
|
||||
### 3. `robots.txt` still points to the dead domain's sitemap
|
||||
- **Severity:** High
|
||||
- **Evidence:** live `https://www.mnqeventos.es/robots.txt` → `Sitemap: https://www.mnqcatering.com/sitemap-index.xml`.
|
||||
- **Recommendation:** Confirm `public/robots.txt` in source now reads correctly (it's generated from the same `site` config via `@astrojs/sitemap`/Astro, so it should self-correct once redeployed) and redeploy.
|
||||
|
||||
### 4. Noindexed URL present in sitemap — `/aviso-legal/` (unchanged by the domain fix)
|
||||
- **Severity:** High
|
||||
- **Evidence:** `src/pages/aviso-legal.astro:8` sets `robots="noindex,follow"` (page is an explicitly-labeled placeholder pending final legal data), but `astro.config.mjs`'s sitemap filter only excludes `/cookies/` and `/privacidad/`, not `/aviso-legal/`. It's present in both the stale live sitemap and the freshly rebuilt one.
|
||||
- **Recommendation:** Add `/aviso-legal/` to the sitemap filter, or remove `noindex` once the page's legal content is finalized.
|
||||
|
||||
### 5. Apex domain redirect unverified live
|
||||
- **Severity:** Medium
|
||||
- **Evidence:** `https://mnqeventos.es/` (no `www`) did not respond from this environment; could not confirm live 301 behavior to `www`.
|
||||
- **Recommendation:** Verify from an unrestricted network: `curl -I https://mnqeventos.es/` should 301 to `https://www.mnqeventos.es/`.
|
||||
|
||||
### 6. Trailing-slash canonical inconsistency risk (code-level, still present after the domain fix)
|
||||
- **Severity:** Medium
|
||||
- **Evidence:** `astro.config.mjs` doesn't set `trailingSlash`; canonical tag in `src/layouts/MainLayout.astro:34` is built from `Astro.url.pathname` (the live request path), so `/aviso-legal` and `/aviso-legal/` could both 200 with differing self-referencing canonicals, while the sitemap only lists the trailing-slash form.
|
||||
- **Recommendation:** Set `trailingSlash: 'always'` and ensure the middleware/hosting layer redirects the non-slash form.
|
||||
|
||||
## XML validity
|
||||
|
||||
- `sitemap-index.xml` and `sitemap-0.xml`, both live and locally-rebuilt versions: well-formed XML, correct namespaces, no `priority`/`changefreq` clutter.
|
||||
- 7 URLs total — nowhere near the 50,000-URL limit; single-file index is appropriate.
|
||||
|
||||
## Quality gates (location pages)
|
||||
|
||||
Not applicable — 0 location pages, no programmatic page generation.
|
||||
|
||||
## What already works well
|
||||
|
||||
- The domain-migration bug is **already fixed in source and committed** (`a6692b7`) — this is a deploy-pipeline gap, not unresolved engineering work.
|
||||
- `/cookies/` and `/privacidad/` are correctly excluded from the sitemap and both carry matching `robots="noindex,follow"` — keep this as-is.
|
||||
- No deprecated `priority`/`changefreq` tags.
|
||||
- Sitemap-index wrapper used despite low URL count — future-proof.
|
||||
- No dynamic/programmatic routes, so no doorway-page risk.
|
||||
|
||||
## Score: 25 / 100 (live, as currently deployed)
|
||||
|
||||
The live sitemap infrastructure is currently non-functional end-to-end (index → sub-sitemap → robots.txt all point to an unregistered domain), which is as severe as it gets for this checklist even though the underlying code quality is otherwise reasonable. **This score will jump to roughly 75/100 immediately upon redeploying the already-committed fix** — the only remaining code-level issues at that point are the `/aviso-legal/` noindex contradiction (High) and the trailing-slash canonical gap (Medium), both pre-existing and unrelated to the domain migration.
|
||||
|
||||
## Immediate action
|
||||
|
||||
Redeploy production from current `master` (commit `a6692b7` or later) so the live sitemap, sub-sitemap, and `robots.txt` all resolve to `mnqeventos.es`. This is the single highest-impact fix available right now.
|
||||
@@ -0,0 +1,149 @@
|
||||
# SXO Audit — mnqeventos.es (MNQ Catering y Evento)
|
||||
|
||||
**Audited URL:** https://www.mnqeventos.es/ (live, verified 2026-09-15)
|
||||
**Pages in scope:** `/` (home), `/bodas-reales`, `/corporativo-business`, `/reuniones-familiares`
|
||||
**Target keywords:** "catering bodas Málaga", "catering eventos corporativos Málaga", "catering premium Málaga"
|
||||
**Method:** Live fetch via `render_page.py --mode auto` + `parse_html.py`, cross-referenced against real Google SERP results (WebSearch) and one competitor page inspection (WebFetch, laborraja.com).
|
||||
|
||||
> **Note on domain history:** the original audit target `mnqcatering.com` is an unregistered domain (WHOIS: no match, NXDOMAIN everywhere). The correct live site is `www.mnqeventos.es`. This was confirmed live and used for this audit.
|
||||
|
||||
---
|
||||
|
||||
## Critical technical caveat (outside SXO scope, but blocks everything below)
|
||||
|
||||
**Every page in scope currently self-references the wrong, unregistered domain** in `<link rel="canonical">`, `og:url`, `og:image`, `twitter:image`, and the `CateringService` JSON-LD `url` field — all point to `https://www.mnqcatering.com/...`, which does not resolve (NXDOMAIN). The footer also displays the text link `www.mnqcatering.com`.
|
||||
|
||||
This means Google's current understanding of "the real URL" for every page it has crawled points at a dead host. No amount of SXO/on-page work matters if canonical/indexing signals are broken — **this should be fixed and redeployed before any other action in this report is prioritized.** The orchestrator noted this is already corrected in the codebase and pending deployment; flagging here because it is what the *live* site still shows as of this audit. Recommend `/seo page` or a technical-SEO pass to confirm the fix covers canonical, OG, Twitter Card, and JSON-LD `url` fields consistently across all templates before redeploying.
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
MNQ's four core pages are **structurally the right page type** — they closely follow the "Service Page" pattern (process/methodology sections, FAQ, testimonials, contact CTA) that dominates the real SERP for these keywords, which is good news: this is not a page-type mismatch requiring a rebuild. The real gap is a **content-completeness and local-anchoring gap**: the pages never mention "Málaga," never state a service area, never give any pricing anchor (not even a "desde X€/persona" range), and never state guest-capacity ranges. Real competitors ranking for the same terms (La Borraja, directories like bodas.net/celebrents.es, and a dedicated "cuánto cuesta un catering de boda en Málaga" guide) either saturate the page with location keywords, publish price ranges, or both. MNQ's copy is polished and emotionally strong (real named-couple testimonials, venue names, sensory dish descriptions) but reads as premium-generic rather than answering the two questions every first-time visitor actually has before contacting: **"Do they cover my area?"** and **"Is this within my budget?"**
|
||||
|
||||
**SXO Gap Score: 59/100** (Needs Work — see breakdown below; separate from any SEO Health Score).
|
||||
|
||||
---
|
||||
|
||||
## SERP Analysis
|
||||
|
||||
Searched: "catering bodas Málaga premium", "catering eventos corporativos Málaga", "catering bodas Málaga precio".
|
||||
|
||||
| Signal | Observation |
|
||||
|---|---|
|
||||
| Dominant result type | Mixed: ~45% marketplace/directory pages (bodas.net, celebrents.es, cateringclick.com, encuentratucatering.es — **Comparison/Local-directory type**), ~45% individual competitor **Service Pages** with strong local anchoring (La Borraja, Smart Catering Málaga, Gorest, Alejandra Catering, Top Food, La Monarca, Catering Lepanto), ~10% informational/price-guide content (Alejandra Catering's "¿Cuánto cuesta realmente un catering de boda en Málaga?"). |
|
||||
| Location saturation | Competitor pages mention "Málaga"/"Costa del Sol" 15+ times each (verified on laborraja.com), including named venues (Archidona, Marbella, Ronda, Churriana). MNQ's 4 pages: **0 mentions of Málaga/Costa del Sol/Andalucía** in body copy, headings, or schema. |
|
||||
| Pricing signal | Multiple ranking pages/directories display explicit price ranges (€69–153/person, avg €70–109/person) or a whole article dedicated to cost breakdown. This is a real, rankable, expected content angle Google is rewarding. MNQ shows **no number anywhere**, on any of the 4 pages — three near-identical FAQ answers just say "se calcula a medida." |
|
||||
| Trust signals on competitors | Even top competitors mostly lack chef bios/certifications too (La Borraja has none) — so MNQ's 2 named-couple testimonials with real venue names on `/bodas-reales` are already **above competitor baseline** on this specific dimension. |
|
||||
| Local pack / NAP | Directories imply strong local-intent SERP; MNQ has PostalAddress/`addressLocality: Málaga` only on `/contacto` (outside audit scope) — none of the 4 in-scope pages carry any local business schema or address. |
|
||||
|
||||
**Dominant SERP page type: Service Page with Local Page characteristics** (location-saturated, venue-specific, sometimes with directory/comparison competition layered on top).
|
||||
|
||||
---
|
||||
|
||||
## Page-Type Classification & Mismatch Severity
|
||||
|
||||
| Page | Classification (taxonomy) | vs. SERP dominant type | Mismatch severity |
|
||||
|---|---|---|---|
|
||||
| `/` (home) | Service Page hub (hero + CTA + 3 service teasers + testimonials) | Correct base type | ALIGNED (structure) |
|
||||
| `/bodas-reales` | Service Page (overview → process → testimonials → FAQ → CTA) — matches taxonomy's Service Page structure almost exactly | Missing the **Local Page** layer (no address, no service-area statement, no Málaga keyword) that real competitors add on top of the same Service Page skeleton | **MEDIUM** |
|
||||
| `/corporativo-business` | Service Page, best-executed of the three (on-page lead form) | Same local-layer gap | **MEDIUM** |
|
||||
| `/reuniones-familiares` | Service Page | Same local-layer gap, plus missing guest-capacity/minimum info | **MEDIUM** |
|
||||
|
||||
**Primary finding: this is not a page-type mismatch (the skeleton is right) — it's a missing Local Page layer + missing pricing transparency**, both of which the real SERP consensus rewards. Treat as MEDIUM severity: structurally sound, functionally incomplete for a local, budget-sensitive buyer journey.
|
||||
|
||||
---
|
||||
|
||||
## User Stories (derived from SERP signals)
|
||||
|
||||
1. **As a bride/groom planning a wedding in Málaga (Awareness→Consideration)**, I want to instantly confirm this caterer serves my area, because I'm scanning several providers quickly, but I'm blocked by **zero mentions of "Málaga" or any service area** anywhere on `/bodas-reales` — the copy could describe a caterer in any city.
|
||||
*(Source: competitor SERP pages mention Málaga/Costa del Sol 15+ times each; MNQ page: 0 mentions.)*
|
||||
|
||||
2. **As a budget-conscious couple (Consideration)**, I want at least a ballpark price per person before messaging a stranger on WhatsApp, because the SERP itself surfaces a dedicated "¿Cuánto cuesta un catering de boda en Málaga?" guide and directory pages publishing €69–153/person ranges, but I'm blocked by MNQ's explicit refusal to give any number ("no trabajamos con paquetes cerrados," "presupuesto a medida" repeated verbatim on 3 pages).
|
||||
*(Source: ranking price-guide article + directory price displays in top-10 SERP.)*
|
||||
|
||||
3. **As a corporate event planner comparing several caterers (Decision)**, I want to see named case studies or client logos specific to corporate events, because comparison fatigue is high in this niche (7+ visually similar "premium" competitors rank), but `/corporativo-business` shows **zero** case studies or logos on the page itself — the only testimonial in the entire site (Javier Ruiz, TechCorp) lives on the homepage and isn't repeated here.
|
||||
*(Source: 7 individual competitor Service Pages co-ranking; page content inspection.)*
|
||||
|
||||
4. **As a couple wanting reassurance before contacting (Consideration→Decision)**, I want to confirm they can actually travel to my specific venue, because `/bodas-reales` *does* build real trust with two named-couple testimonials and real venue names (Finca La Montaña, Castillo de Viñuelas) — a genuine strength — but coverage is only ever described as "cobertura a confirmar," leaving the geography question open even after reading the FAQ.
|
||||
*(Source: page FAQ "¿Cuándo se confirma la viabilidad del evento?" answers process, never geography.)*
|
||||
|
||||
5. **As a family organizing a baptism or anniversary (Awareness)**, I want to know roughly how many guests they can handle and whether my gathering is "big enough" or "too big," because `/reuniones-familiares` clearly lists formats (Brunch, comida sentada, cóctel, cena privada) — good clarity — but never states a minimum or maximum guest count or minimum spend, so I don't know if I qualify before reaching out.
|
||||
*(Source: page content inspection — no numeric guest range anywhere on the page.)*
|
||||
|
||||
Stories span Awareness (1, 5), Consideration (2, 3), and Decision (4) — 3 journey stages covered.
|
||||
|
||||
---
|
||||
|
||||
## Persona Scoring
|
||||
|
||||
| Persona | Target page | Relevance | Clarity | Trust | Action | Total | Rating |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| Novia/Novio premium en Málaga | `/bodas-reales` | 20/25 | 14/25 | 18/25 | 16/25 | **68/100** | Good |
|
||||
| Responsable corporativo comparando proveedores | `/corporativo-business` | 21/25 | 17/25 | 12/25 | 22/25 | **72/100** | Good |
|
||||
| Familia organizando bautizo/aniversario | `/reuniones-familiares` | 19/25 | 13/25 | 15/25 | 17/25 | **64/100** | Good (borderline) |
|
||||
| Comprador sensible al precio (cross-site) | all 4 | 10/25 | 6/25 | 10/25 | 14/25 | **40/100** | Critical Mismatch |
|
||||
| Buscador en Costa del Sol fuera de Málaga capital | all 4 | 12/25 | 8/25 | 12/25 | 15/25 | **47/100** | Needs Work |
|
||||
| Wedding/event planner profesional evaluando proveedor | `/corporativo-business` + home | 17/25 | 12/25 | 14/25 | 13/25 | **56/100** | Needs Work |
|
||||
|
||||
### Weakest Persona: Comprador sensible al precio (40/100)
|
||||
**Top issue:** No page in scope gives any numeric price anchor — not a range, not a "desde X€/persona," nothing. Three FAQ answers across three different pages use near-identical wording to deflect the cost question entirely.
|
||||
**Recommended fix:** Add a transparent-but-vague anchor that preserves the bespoke positioning without scaring off budget-checkers — e.g., a line under the FAQ "¿El presupuesto incluye solo comida?" stating an indicative starting range (mirrors the €69–153/pp band competitors already publish), or a "solicite rango orientativo" micro-CTA that's lower-friction than a full WhatsApp message.
|
||||
|
||||
### Systemic Issue: Clarity is the weakest dimension across every persona
|
||||
Average Clarity score ≈ 11.7/25 vs. Relevance ≈ 16.5/25. Root causes, both fixable at the content level:
|
||||
- No numeric anchors anywhere (price, guest capacity, service radius) — every "how much / how many / how far" question requires initiating contact to answer.
|
||||
- The three FAQ blocks (bodas, corporativo, familiares) reuse nearly identical template language ("se calcula según... invitados, personal, menaje, timing, desplazamiento") instead of giving page-specific concrete numbers or examples.
|
||||
|
||||
### Priority Actions
|
||||
1. Add an indicative price range (or minimum spend) near the budget FAQ on all three service pages — targets the weakest persona (Comprador sensible al precio, 40/100).
|
||||
2. Add explicit service-area copy ("Servicio en Málaga capital y Costa del Sol — Marbella, Ronda, Estepona...") to the hero or a dedicated info block on all 3 service pages and the homepage — targets both the location-uncertainty persona (47/100) and the SERP's dominant location-saturation signal.
|
||||
3. Add a guest-count range or minimum to `/reuniones-familiares` and `/bodas-reales` — closes the second-most-cited clarity gap.
|
||||
4. Add at least one named case study/logo to `/corporativo-business` — targets the professional-planner and corporate personas (56/100, 72/100) and directly addresses comparison fatigue from 7+ visually similar competitors.
|
||||
|
||||
---
|
||||
|
||||
## Gap Analysis (7 dimensions, 100 pts)
|
||||
|
||||
| Dimension | Score | Evidence |
|
||||
|---|---|---|
|
||||
| Page Type (0–15) | 12/15 | Correct Service Page skeleton (process, FAQ, testimonials, CTA) matching SERP consensus; missing the Local Page layer competitors add. |
|
||||
| Content Depth (0–15) | 7/15 | 358–488 words/page; no pricing, no capacity, no service-area detail — thin vs. the depth a "guía de presupuesto" competitor article demonstrates ranks well for these terms. |
|
||||
| UX Signals (0–15) | 10/15 | Clear headings, working FAQ accordions, `/corporativo-business` has an actual lead-capture form (best element on the site); but WhatsApp-only CTA on the other two pages, and no low-commitment alternative (menu download, price calculator). |
|
||||
| Schema (0–15) | 8/15 | `CateringService` + `FAQPage` JSON-LD present on all 3 service pages (good); but `url` field hard-codes the unregistered domain, no address/areaServed/geo despite being a local business, no Review/AggregateRating schema despite having testimonials. |
|
||||
| Media (0–15) | 12/15 | 6–13 images per page, descriptive Spanish alt text, dedicated gallery on homepage; missing chef/team photos and photos tied to the named testimonials/venues. |
|
||||
| Authority (0–15) | 5/15 | Only 2 named testimonials total (on `/bodas-reales`) + 1 generic corporate quote on the homepage not reused elsewhere; zero press mentions, awards, certifications, or "X eventos realizados" counters (competitor La Monarca advertises "+300 eventos/año"). |
|
||||
| Freshness (0–10) | 5/10 | Footer says "© 2025" and testimonials are dated (Junio/Septiembre 2023) but look stale by comparison; no visible "recently updated" or current-year case study signal. |
|
||||
| **Total** | **59/100** | |
|
||||
|
||||
**SXO Gap Score: 59/100** (this is separate from and not comparable to any SEO Health Score).
|
||||
|
||||
---
|
||||
|
||||
## What already works well
|
||||
|
||||
- **Correct page-type skeleton**: all 3 service pages already follow the Service Page structure the real SERP rewards (process/methodology sections, FAQ, testimonial, CTA) — no rebuild needed, just content completion.
|
||||
- **Real, specific testimonials**: named couples + real venue names (Finca La Montaña, Castillo de Viñuelas) on `/bodas-reales` are stronger trust signals than at least one top-ranking competitor (La Borraja has none at all).
|
||||
- **`/corporativo-business` lead form**: the only page with a proper on-page form (Empresa, Correo Corporativo, Tipo de Evento, Nº Asistentes) instead of relying solely on WhatsApp — meaningfully lower friction and the strongest-performing page in persona scoring (72/100).
|
||||
- **FAQPage + CateringService structured data present and valid** on all 3 service pages — most competitors inspected don't appear to have this.
|
||||
- **Descriptive, consistent image alt text** in Spanish across all pages (13 images on homepage alone, all with meaningful alt attributes).
|
||||
- **Consistent CTA presence**: every page has at least one clear, above-the-fold-reachable WhatsApp CTA.
|
||||
|
||||
---
|
||||
|
||||
## Limitations
|
||||
|
||||
- Only the 4 requested pages (`/`, `/bodas-reales`, `/corporativo-business`, `/reuniones-familiares`) were fetched and parsed; `/contacto` and `/nosotros` were referenced only via nav links, not audited in depth (contacto.html data available from a prior pass in this session shows `addressLocality: Málaga` in schema there, suggesting the address exists but isn't surfaced on the pages that actually need it for buyer decision-making).
|
||||
- SERP analysis is based on 3 representative WebSearch queries (~9 unique top-10 URLs) plus one deep competitor page inspection (laborraja.com), not a full manual SERP crawl with PAA/AI Overview/ads capture — Google's live PAA boxes and AI Overview content for these exact queries could not be directly inspected through the available tools.
|
||||
- No rank-tracking or Search Console data was available to confirm MNQ's current ranking position or click-through behavior for these keywords.
|
||||
- Mobile-rendered/JS-rendered DOM was not force-rendered (`--mode always`); pages returned `is_spa: false` and raw HTML matched expected content, so this is a low-risk gap, but above-the-fold visual verification (Lighthouse/screenshot) was not performed in this pass.
|
||||
- The canonical/OG/schema domain-mismatch issue is reported here because it was observed live, but a full technical-SEO remediation check is out of scope for this SXO audit — recommend a dedicated technical pass after the pending domain-fix deploy.
|
||||
|
||||
---
|
||||
|
||||
## Recommended follow-ups
|
||||
|
||||
- `/seo schema` — add `areaServed`/`address`/`geo` to the `CateringService` JSON-LD on all 3 service pages, add `Review`/`AggregateRating` for the testimonials, and fix the `url` field once the domain fix is deployed.
|
||||
- `/seo local` — formalize service-area messaging (Málaga capital + Costa del Sol towns) across the 3 service pages and homepage, not just `/contacto`.
|
||||
- `/seo content` — add pricing-range and guest-capacity content blocks; expand `/corporativo-business` with a named case study.
|
||||
- Generate a PDF report? Use `/seo google report`.
|
||||
@@ -0,0 +1,126 @@
|
||||
# Technical SEO Audit — MNQ Catering y Evento
|
||||
|
||||
**Audited live site:** https://www.mnqeventos.es/ (confirmed canonical/production domain — see Finding #1)
|
||||
**Date:** 2026-09-15
|
||||
**Method:** DNS (dig, WHOIS), curl (headers/status/redirects), HTML source inspection. No JS rendering / Lighthouse run performed (out of scope).
|
||||
|
||||
## Summary
|
||||
|
||||
The domain originally given for this audit, `mnqcatering.com` / `www.mnqcatering.com`, **does not exist** — confirmed via WHOIS ("No match for domain MNQCATERING.COM") and authoritative DNS (NXDOMAIN). This was corrected mid-audit by the coordinator: the real, live, production domain is **https://www.mnqeventos.es/** (bare apex `mnqeventos.es` has no A record — only the `www` host resolves and serves).
|
||||
|
||||
The codebase was fixed **in this session** (commit `a6692b7`, working tree at audit time) to set `CANONICAL_HOST = 'www.mnqeventos.es'` in `src/middleware.ts` and `site: 'https://www.mnqeventos.es'` in `astro.config.mjs`. **This fix is not yet deployed**: every live page fetched during this audit still emits canonical tags, the sitemap, robots.txt, JSON-LD `url`, and `og:url`/`og:image` pointing at `www.mnqcatering.com` — a domain that cannot resolve. This is the single highest-priority issue in the audit.
|
||||
|
||||
## Findings
|
||||
|
||||
### 1. [Critical] Declared canonical domain (`www.mnqcatering.com`) does not exist in DNS/WHOIS
|
||||
**Evidence:**
|
||||
```
|
||||
$ whois mnqcatering.com
|
||||
No match for domain "MNQCATERING.COM".
|
||||
|
||||
$ dig www.mnqcatering.com A +noall +comments
|
||||
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN
|
||||
```
|
||||
**Recommendation:** Confirmed non-bug in the audit sense (per coordinator) — the domain was never registered. Action item is organizational, not technical: either register `mnqcatering.com` and point it at the same infrastructure with a 301 to `www.mnqeventos.es`, or fully scrub any remaining reference to it (marketing collateral, external backlinks, business cards, Google Business Profile, etc.) since it's a dead domain that currently returns nothing.
|
||||
|
||||
### 2. [Critical] Production is still serving the pre-fix build — every canonical/sitemap/structured-data reference points to the non-existent domain
|
||||
**Evidence** (all fetched live from `https://www.mnqeventos.es/` on 2026-09-15):
|
||||
- Canonical tags on all 9 pages, e.g.:
|
||||
`<link rel="canonical" href="https://www.mnqcatering.com/">`
|
||||
`<link rel="canonical" href="https://www.mnqcatering.com/aviso-legal">`
|
||||
- `robots.txt`:
|
||||
```
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
Sitemap: https://www.mnqcatering.com/sitemap-index.xml
|
||||
```
|
||||
- `sitemap-index.xml` → `<loc>https://www.mnqcatering.com/sitemap-0.xml</loc>` → `sitemap-0.xml` lists 7 URLs, all on `https://www.mnqcatering.com/...`
|
||||
- JSON-LD (`CateringService`): `"url":"https://www.mnqcatering.com"`
|
||||
- OpenGraph: `og:url` and `og:image` both on `https://www.mnqcatering.com/...`
|
||||
- The application-layer redirect middleware (which should force any non-canonical `Host` to the canonical one) is **not firing** in production: `https://www.mnqeventos.es/` returns `200` directly instead of a `301` — consistent with the live deployment predating the `a6692b7` fix, since that fix only changed which host is canonical, not whether the middleware runs.
|
||||
|
||||
Repo state confirms the fix exists in source but hasn't shipped:
|
||||
```
|
||||
src/middleware.ts:3:const CANONICAL_HOST = 'www.mnqeventos.es';
|
||||
astro.config.mjs:6: site: 'https://www.mnqeventos.es',
|
||||
```
|
||||
```
|
||||
$ git log --oneline -3
|
||||
a6692b7 fix(seo): correct canonical domain to www.mnqeventos.es
|
||||
0e242a6 docs(seo): add search-engine publication and local SEO checklist
|
||||
5741e68 fix(seo): redirect non-canonical domains to www.mnqcatering.com
|
||||
```
|
||||
**Impact:** Every canonical signal Google/Bing can currently read is unreachable. Search engines will either ignore the canonical hint entirely (falling back to the URL actually crawled, `www.mnqeventos.es`) or, worse, treat it as a soft error during indexing. The sitemap is effectively useless since its URLs 404-by-DNS. This blocks clean re-indexing under the correct domain.
|
||||
**Recommendation:** Deploy the current `master`/working-tree build immediately. Post-deploy, re-verify: canonical tags, sitemap URLs, robots.txt `Sitemap:` line, JSON-LD `url`, and `og:url`/`og:image` should all read `https://www.mnqeventos.es/...`. Then submit the updated sitemap in Search Console/Bing Webmaster Tools and request re-indexing of all 7 indexable URLs.
|
||||
|
||||
### 3. [High] No security headers on any response
|
||||
**Evidence:**
|
||||
```
|
||||
$ curl -s -D - -o /dev/null --http1.1 https://www.mnqeventos.es/nosotros
|
||||
HTTP/1.1 200 OK
|
||||
Alt-Svc: h3=":443"; ma=2592000
|
||||
Content-Type: text/html
|
||||
Date: Tue, 15 Sep 2026 07:14:49 GMT
|
||||
Transfer-Encoding: chunked
|
||||
```
|
||||
No `Strict-Transport-Security`, `X-Content-Type-Options`, `X-Frame-Options`, `Content-Security-Policy`, `Referrer-Policy`, or `Permissions-Policy` header on any of the 9 pages or `robots.txt`/`sitemap-index.xml`. No `Content-Encoding` returned even when requesting `Accept-Encoding: gzip, br, deflate` — responses are served uncompressed (or the header is stripped at the edge; either way it's not observable from outside).
|
||||
**Recommendation:** Add at minimum `Strict-Transport-Security: max-age=31536000; includeSubDomains`, `X-Content-Type-Options: nosniff`, `X-Frame-Options: DENY` (or CSP `frame-ancestors`), and a baseline CSP. These aren't primarily ranking factors but HSTS absence is a real security/trust gap, and Google's security-signals tooling (and some AI crawlers) do surface missing headers. Also verify/enable br or gzip compression at the CDN/edge — it affects transfer time and thus LCP on slower connections.
|
||||
|
||||
### 4. [Medium] Trailing-slash URL duplication not canonicalized at the redirect layer
|
||||
**Evidence:**
|
||||
```
|
||||
$ curl -s -o /dev/null -w "%{http_code}\n" https://www.mnqeventos.es/aviso-legal
|
||||
200
|
||||
$ curl -s -o /dev/null -w "%{http_code}\n" https://www.mnqeventos.es/aviso-legal/
|
||||
200
|
||||
```
|
||||
Both variants return `200` directly (no redirect either way), yet the sitemap lists the trailing-slash form (`https://www.mnqcatering.com/aviso-legal/`) while the page's own canonical tag declares the no-slash form (`https://www.mnqcatering.com/aviso-legal`) — the two signals disagree with each other, independent of the domain issue in Finding #2.
|
||||
**Recommendation:** Pick one form (recommend no trailing slash, matching the existing canonical tags) and 301-redirect the other at the middleware/edge level; make sure the sitemap generator emits the same form as the canonical tags.
|
||||
|
||||
### 5. [Info/Pass] Meta robots correct across all 9 pages
|
||||
**Evidence:** `index,follow` on `/`, `/nosotros`, `/bodas-reales`, `/corporativo-business`, `/reuniones-familiares`, `/contacto`; `noindex,follow` on `/cookies`, `/privacidad`, `/aviso-legal`. The sitemap correctly excludes the three noindexed legal pages (7 URLs listed, matching the 6 index,follow pages + none of the 3 noindex pages — actually 7 = 6 main + apex counted once; legal pages correctly absent).
|
||||
No issue — this is exactly the intended pattern.
|
||||
|
||||
### 6. [Info/Pass] No 404s or broken internal links found
|
||||
**Evidence:** All 9 requested paths return `200`. All `href="/..."` internal links extracted from all 9 pages resolve to one of those same 9 known paths (no dead links, no orphaned nav targets). A deliberately invalid path returns a real `404`:
|
||||
```
|
||||
$ curl -s -o /dev/null -w "%{http_code}\n" https://www.mnqeventos.es/this-page-does-not-exist-xyz
|
||||
404
|
||||
```
|
||||
|
||||
### 7. [Info/Pass] TLS certificate valid, but scoped only to the real domain
|
||||
**Evidence:**
|
||||
```
|
||||
subject=CN=www.mnqeventos.es
|
||||
issuer=C=US, O=Let's Encrypt, CN=YR2
|
||||
notBefore=Aug 15 10:48:30 2026 GMT
|
||||
notAfter=Nov 13 10:48:29 2026 GMT
|
||||
X509v3 Subject Alternative Name: DNS:www.mnqeventos.es
|
||||
```
|
||||
Valid, auto-renewing (Let's Encrypt ~90-day cert, well within window). SAN correctly does **not** include `mnqcatering.com` (expected, since that domain doesn't exist — not a cert misconfiguration).
|
||||
No action needed beyond normal renewal monitoring.
|
||||
|
||||
### 8. [Info/Pass] Core Web Vitals signals look solid from source inspection
|
||||
**Evidence:** Hero/LCP image has `loading="eager" fetchpriority="high" decoding="async"` with explicit `width`/`height`; all other images are `loading="lazy"` with explicit dimensions (protects CLS); web fonts use the `preload` + `onload` swap-to-stylesheet pattern (avoids render-blocking); the one third-party script (analytics, `umami.qreastech.com`) is `async defer`; zero inline blocking `<script>` tags; homepage HTML is a lean 25KB. No obvious CWV red flags from static inspection — a real Lighthouse/CrUX run is still recommended to confirm actual LCP/INP/CLS values, but nothing in source suggests a problem.
|
||||
|
||||
### 9. [Not applicable / blocked] IndexNow
|
||||
`indexnow.txt`/key file not found (`404`). Given Finding #2, implementing IndexNow now (while canonical URLs point at a dead domain) would be counterproductive — pings would announce URLs on the wrong host. **Recommendation:** implement IndexNow only after Finding #2 is deployed and re-verified, submitting `https://www.mnqeventos.es/...` URLs.
|
||||
|
||||
## Redirect Behavior Matrix
|
||||
|
||||
| Request | Result |
|
||||
|---|---|
|
||||
| `http://mnqeventos.es/` (bare, http) | DNS: `NOERROR`, 0 answers — apex has no A record, connection fails |
|
||||
| `https://mnqeventos.es/` (bare, https) | Same — apex does not serve |
|
||||
| `http://www.mnqeventos.es/` | `301` → `https://www.mnqeventos.es/` (HTTP→HTTPS enforced, good) |
|
||||
| `https://www.mnqeventos.es/` | `200` direct (no further redirect — this is the live site) |
|
||||
| `http(s)://mnqcatering.com/`, `http(s)://www.mnqcatering.com/` | Cannot resolve — domain not registered (Finding #1); not a redirect issue |
|
||||
|
||||
The canonical-host middleware described as "just shipped" only redirects **away from** `www.mnqcatering.com`/`mnqeventos.es` (bare) **to** the `CANONICAL_HOST` constant — it was never expected to make the bare `mnqeventos.es` apex resolve (that's a DNS-level gap, not something app middleware can fix; the apex needs its own A/AAAA record or a redirect at the DNS/registrar level if traffic to the bare domain is expected).
|
||||
|
||||
## Score: 58 / 100
|
||||
|
||||
**What already works well:** page-level fundamentals are solid — correct `index,follow` vs `noindex,follow` split, no broken internal links or unexpected 404s, valid and current TLS certificate, and CWV-relevant markup (image dimensions, lazy-loading, non-blocking fonts/scripts) all look properly implemented in the Astro source. The domain-identity fix has already been correctly authored in code.
|
||||
|
||||
**What's dragging the score down:** the fix isn't deployed, so right now *every single indexing signal the site emits* (canonical, sitemap, robots.txt sitemap pointer, JSON-LD, OpenGraph) points at a domain that returns NXDOMAIN — which for a live production site is as severe as having no canonical at all — compounded by a total absence of security headers.
|
||||
@@ -0,0 +1,49 @@
|
||||
# Visual / Above-the-Fold Audit — mnqcatering.com
|
||||
|
||||
## Summary
|
||||
|
||||
The audit target, `https://www.mnqcatering.com`, **does not resolve**. No visual, above-the-fold, mobile-rendering, or CTA/overlap analysis could be performed because the site is unreachable — this is not a tooling limitation, it is a state of the live domain itself. No screenshots were captured and none of the requested visual assessments (CTA clarity, tap-target sizing, layout shift, WhatsApp/cookie-banner overlap) could be verified. Do not treat the absence of findings below as "no issues" — it means "not observable."
|
||||
|
||||
## Verification performed
|
||||
|
||||
Playwright/Chromium was successfully installed and configured in this session (in an isolated venv), so tooling was not the blocker. Navigation to `https://www.mnqcatering.com` failed with `net::ERR_NAME_NOT_RESOLVED`. This was cross-checked independently of the browser:
|
||||
|
||||
- `nslookup www.mnqcatering.com` (local resolver) → `NXDOMAIN`
|
||||
- `dig mnqcatering.com @8.8.8.8` and `@1.1.1.1` (public resolvers, bypassing any local/sandbox DNS) → `NXDOMAIN`
|
||||
- `whois mnqcatering.com` → **"No match for domain 'MNQCATERING.COM'"** (Verisign registry has no registration record for this domain at all — this is not "DNS not yet propagated," it means the `.com` is currently unregistered/available)
|
||||
|
||||
This confirms the failure is a real domain/DNS state, not a sandbox network artifact.
|
||||
|
||||
## Codebase cross-check
|
||||
|
||||
The repository is configured with `www.mnqcatering.com` as the intended canonical host:
|
||||
|
||||
- `astro.config.mjs`: `site: 'https://www.mnqcatering.com'`
|
||||
- `src/middleware.ts`: `CANONICAL_HOST = 'www.mnqcatering.com'`, with a comment noting the site was "undesirably mirrored at mnqeventos.es (same content, no redirect at the DNS/hosting level)" and enforcing app-layer redirects to the `.com` host for any other incoming host (including `mnqeventos.es`, `www.mnqeventos.es`, and the bare `mnqcatering.com` apex)
|
||||
|
||||
This means the middleware's entire redirect strategy currently funnels traffic toward a domain that is not registered — if that middleware is live anywhere, it is actively redirecting visitors to a dead destination.
|
||||
|
||||
### `mnqeventos.es` (the alternate host referenced in the code)
|
||||
|
||||
- `dig mnqeventos.es @8.8.8.8` → resolves to `169.58.117.246` (domain is registered and has DNS records)
|
||||
- An HTTP reachability check from this sandbox to `mnqeventos.es` failed at the network layer (could not resolve/connect from this specific execution environment, despite public DNS resolving it), so I could **not** confirm `mnqeventos.es` is actually serving the live site, nor capture screenshots of it. This is a sandbox network-egress limitation for that particular check, distinct from the `mnqcatering.com` finding, which is a confirmed WHOIS/registry-level absence.
|
||||
|
||||
## Findings
|
||||
|
||||
| # | Severity | Evidence | Recommendation |
|
||||
|---|----------|----------|-----------------|
|
||||
| 1 | **Critical** | `whois mnqcatering.com` → "No match for domain 'MNQCATERING.COM'"; `dig` against 8.8.8.8 and 1.1.1.1 both return NXDOMAIN | The canonical domain the entire site (config, middleware, sitemap, canonical tags) is built around is not currently registered. Register/renew `mnqcatering.com` immediately, or confirm with the registrar whether this is an expiry lapse. Until resolved, the site has zero organic visibility on its intended domain — this supersedes every other SEO/visual concern audited elsewhere in this project. |
|
||||
| 2 | **High** (unverified, code-derived) | `src/middleware.ts` redirects all non-canonical hosts (including `mnqeventos.es`) to `www.mnqcatering.com` with a 301 | If this middleware is deployed and `mnqeventos.es` is the domain actually receiving traffic, that traffic is being 301-redirected to an unregistered domain — a hard dead end for users and a guaranteed crawl error for search engines. Verify current production deployment target before doing anything else. |
|
||||
| 3 | Info | N/A — no page could be loaded | All requested visual checks (above-fold CTA visibility, mobile tap-target sizing ≥48x48px, layout-shift risk, WhatsApp floating button vs. cookie-banner overlap) are **unverified**, not "passed." Re-run this audit once the domain resolves. |
|
||||
|
||||
## Score
|
||||
|
||||
**0 / 100** — reason: primary audit target domain does not resolve (unregistered per WHOIS). No page content was reachable, so no visual/UX quality can be assessed or scored on its merits; a non-resolving domain is a full blocker for any user or crawler reaching the site.
|
||||
|
||||
## What already works well
|
||||
|
||||
Cannot be assessed — no page rendered. The one positive observation available from static inspection is that the **codebase's intent** is coherent: a single canonical host is configured consistently across `astro.config.mjs`, `MainLayout.astro`, and `middleware.ts`, and there's already institutional awareness of the `mnqeventos.es` duplicate-content risk (captured in code comments and a prior "redirect non-canonical domains" commit). The problem is not the code's design — it's that the domain the code assumes is live is not currently registered.
|
||||
|
||||
## Next step
|
||||
|
||||
This is a domain-registration/DNS-infrastructure issue, outside the scope of code or content changes. Resolve domain registration first, then re-run this visual audit (desktop 1440px / mobile 390px screenshots of homepage and `/bodas-reales`, CTA/tap-target/overlap checks) against a confirmed-live host.
|
||||
@@ -0,0 +1,75 @@
|
||||
# Publicación en buscadores — checklist
|
||||
|
||||
Estado: preparado el 2026-09-15. Todo lo de código ya está commiteado y en GitHub
|
||||
(`origin`, `master`). Los pasos de abajo requieren acceso a cuentas externas
|
||||
(Google, Bing, Gitea) que Claude no tiene — hay que ejecutarlos manualmente,
|
||||
salvo donde se indica que Claude puede terminar el trabajo con un dato tuyo.
|
||||
|
||||
## 1. Google Search Console
|
||||
|
||||
1. Entrar a https://search.google.com/search-console y agregar la propiedad
|
||||
`https://www.mnqcatering.com` (tipo "Prefijo de URL", no "Dominio", porque
|
||||
el dominio de verificación por DNS es más lento de configurar).
|
||||
2. Elegir método de verificación:
|
||||
- **Meta tag HTML (recomendado, más rápido):** Google te da una línea
|
||||
`<meta name="google-site-verification" content="XXXX" />`. Pasame el
|
||||
valor de `content` y lo agrego a `src/layouts/MainLayout.astro` en el
|
||||
`<head>`, hago commit y push, y confirmás la verificación en la consola.
|
||||
- **DNS TXT:** si preferís este método, agregás el registro TXT que te da
|
||||
Google en el proveedor de DNS del dominio. No requiere cambios de código.
|
||||
3. Una vez verificado, enviar el sitemap: `Sitemaps` → agregar
|
||||
`sitemap-index.xml` (se genera solo en cada build, en
|
||||
`https://www.mnqcatering.com/sitemap-index.xml`).
|
||||
4. Revisar `Cobertura`/`Páginas` a los pocos días para confirmar indexación de
|
||||
las 6 páginas comerciales (`/`, `/nosotros`, `/bodas-reales`,
|
||||
`/corporativo-business`, `/reuniones-familiares`, `/contacto`).
|
||||
|
||||
## 2. Google Business Profile (clave para SEO local)
|
||||
|
||||
1. Entrar a https://business.google.com y crear/reclamar el perfil de
|
||||
"MNQ Catering y Evento".
|
||||
2. Dirección exacta a usar (debe coincidir con la del sitio, ya cargada en
|
||||
el schema y en footer/contacto):
|
||||
`Calle Camino Vivero, 6, 29014, Málaga, España`
|
||||
3. Categoría principal: "Servicio de catering" (o la más específica que
|
||||
ofrezca Google en español).
|
||||
4. Teléfono: `+34 678 17 15 13` (mismo que en el sitio).
|
||||
5. Web: `https://www.mnqcatering.com`
|
||||
6. Verificar el perfil (Google suele ofrecer verificación por video, código
|
||||
postal o llamada telefónica para este tipo de negocio).
|
||||
7. Una vez verificado, anotame las **coordenadas exactas** que Google asigna
|
||||
al fijar el pin — con eso completo el campo `geo` (`GeoCoordinates`) que
|
||||
quedó pendiente en el JSON-LD.
|
||||
8. Subir fotos reales (ya hay material en `docs/imagenes/`), y activar
|
||||
mensajería/WhatsApp si Google lo permite para esta categoría.
|
||||
|
||||
## 3. Bing Webmaster Tools
|
||||
|
||||
1. Entrar a https://www.bing.com/webmasters
|
||||
2. Usar la opción "Importar desde Google Search Console" (requiere haber
|
||||
completado el paso 1) — importa verificación y sitemap en un solo paso,
|
||||
sin volver a tocar código.
|
||||
|
||||
## 4. Repositorio Gitea (`git.qreastech.com`)
|
||||
|
||||
Pendiente por falta de credenciales: no hay `credential.helper` configurado
|
||||
para ese host en esta máquina. Para destrabarlo:
|
||||
|
||||
- Generar un token de acceso personal en Gitea (`Settings` → `Applications` →
|
||||
`Generate New Token`, con permiso de escritura sobre el repo).
|
||||
- Pasarme el token (o el usuario+token) y lo guardo en el credential store de
|
||||
git para este host únicamente.
|
||||
- El remoto ya está corregido: `https://git.qreastech.com/qreastech/MNQ-Catering-y-Evento.git`
|
||||
(el repo se había movido de namespace `manuyasm87` a `qreastech`).
|
||||
- Los commits ya están listos localmente y en GitHub; solo falta este push.
|
||||
|
||||
## 5. Datos pendientes para profundizar AEO (cuando los tengas)
|
||||
|
||||
- Coordenadas geo exactas (paso 2.7 arriba)
|
||||
- Rango de precios o "a partir de" por tipo de evento
|
||||
- Rango de capacidad (mínimo/máximo de invitados)
|
||||
- Perfiles sociales reales (Instagram, Facebook) para `sameAs` en el schema
|
||||
- Tiempo de antelación mínimo para reservar
|
||||
|
||||
Con cualquiera de estos datos puedo ampliar las FAQ y el schema sin necesidad
|
||||
de otra ronda de preguntas — los agrego apenas los tenga.
|
||||
+1
-1
@@ -4,7 +4,7 @@ HOST = "0.0.0.0"
|
||||
PORT = "4321"
|
||||
|
||||
[phases.setup]
|
||||
nixPkgs = ["nodejs_20"]
|
||||
nixPkgs = ["nodejs_22"]
|
||||
|
||||
[start]
|
||||
cmd = "node ./dist/server/entry.mjs"
|
||||
|
||||
Generated
+1672
-3037
File diff suppressed because it is too large
Load Diff
+5
-5
@@ -12,12 +12,12 @@
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@astrojs/node": "^9.5.5",
|
||||
"@astrojs/sitemap": "^3.7.3",
|
||||
"astro": "^5.18.1"
|
||||
"@astrojs/node": "^11.1.5",
|
||||
"@astrojs/sitemap": "^3.7.4",
|
||||
"astro": "^7.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/check": "^0.9.9",
|
||||
"typescript": "^5.9.3"
|
||||
"@astrojs/check": "^0.9.10",
|
||||
"typescript": "^6.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -1,7 +1,9 @@
|
||||
MNQ Catering y Evento
|
||||
|
||||
- Canonical domain: https://www.mnqcatering.com
|
||||
- Canonical domain: https://www.mnqeventos.es
|
||||
- Business type: Premium catering service
|
||||
- Address: Calle Borde Alegre, 1, 29014, Málaga, España
|
||||
- Area served: Málaga, Marbella, Benalmádena, Fuengirola, Torremolinos, Provincia de Málaga, Costa del Sol
|
||||
- Services: Bodas, eventos corporativos, reuniones familiares
|
||||
- Primary contact channels: WhatsApp (+34 678 17 15 13), phone (+34 678 17 15 13)
|
||||
- Key pages: /, /nosotros, /bodas-reales, /corporativo-business, /reuniones-familiares, /contacto
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
Sitemap: https://www.mnqcatering.com/sitemap-index.xml
|
||||
Sitemap: https://www.mnqeventos.es/sitemap-index.xml
|
||||
|
||||
@@ -7,10 +7,10 @@ const whatsappAriaLabel = 'Contactar con MNQ por WhatsApp';
|
||||
<div class="footer-inner">
|
||||
|
||||
<div class="footer-brand">
|
||||
<a href="/" aria-label="MNQ Catering y Eventos">
|
||||
<a href="/" aria-label="MNQ Catering y Evento">
|
||||
<img
|
||||
src="/images/logo-secondary.webp"
|
||||
alt="MNQ Catering y Eventos"
|
||||
alt="MNQ Catering y Evento"
|
||||
width="224"
|
||||
height="224"
|
||||
style="height: 3.5rem; width: auto; mix-blend-mode: multiply; margin-bottom: 1rem;"
|
||||
@@ -19,7 +19,8 @@ const whatsappAriaLabel = 'Contactar con MNQ por WhatsApp';
|
||||
<p>© 2025 MNQ Catering y Evento. Excelencia culinaria para los momentos más importantes de su vida.</p>
|
||||
<div style="display:flex;flex-direction:column;gap:0.35rem;margin-top:var(--space-4);">
|
||||
<a href="tel:+34678171513" class="footer-link" style="width:fit-content;">+34 678 17 15 13</a>
|
||||
<a href="https://www.mnqcatering.com" class="footer-link" style="width:fit-content;">www.mnqcatering.com</a>
|
||||
<a href="https://www.mnqeventos.es" class="footer-link" style="width:fit-content;">www.mnqeventos.es</a>
|
||||
<p class="text-body-sm" style="width:fit-content;">Calle Borde Alegre, 1, 29014, Málaga</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -17,10 +17,10 @@ const links = [
|
||||
<header class="site-header">
|
||||
<div class="page-wrap">
|
||||
|
||||
<a href="/" class="site-brand-link" aria-label="MNQ Catering y Eventos — inicio">
|
||||
<a href="/" class="site-brand-link" aria-label="MNQ Catering y Evento — inicio">
|
||||
<img
|
||||
src="/images/logo-nav-dark.webp"
|
||||
alt="MNQ Catering y Eventos"
|
||||
alt="MNQ Catering y Evento"
|
||||
class="site-logo-banner"
|
||||
width="600"
|
||||
height="232"
|
||||
|
||||
@@ -30,18 +30,61 @@ const {
|
||||
structuredData,
|
||||
} = Astro.props;
|
||||
|
||||
const site = Astro.site ?? new URL('https://www.mnqcatering.com');
|
||||
const site = Astro.site ?? new URL('https://www.mnqeventos.es');
|
||||
const canonicalUrl = new URL(canonical ?? Astro.url.pathname, site).toString();
|
||||
const ogImageUrl = new URL(ogImage, site).toString();
|
||||
const baseStructuredData = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'CateringService',
|
||||
// schema.org has no "CateringService" type. CateringBusiness (a
|
||||
// LocalBusiness/FoodEstablishment subtype) is the correct type for a
|
||||
// catering company with a physical address.
|
||||
'@type': 'CateringBusiness',
|
||||
name: 'MNQ Catering y Evento',
|
||||
url: site.origin,
|
||||
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'],
|
||||
telephone: '+34678171513',
|
||||
// makesOffer/Service is the correct structure for what a business offers;
|
||||
// serviceType is a property of Service, not of LocalBusiness.
|
||||
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' },
|
||||
},
|
||||
],
|
||||
// Verified business address (owner-confirmed).
|
||||
address: {
|
||||
'@type': 'PostalAddress',
|
||||
streetAddress: 'Calle Borde Alegre, 1',
|
||||
postalCode: '29014',
|
||||
addressLocality: 'Málaga',
|
||||
addressRegion: 'Málaga',
|
||||
addressCountry: 'ES',
|
||||
},
|
||||
// Geocoded from the address above via OpenStreetMap/Nominatim.
|
||||
geo: {
|
||||
'@type': 'GeoCoordinates',
|
||||
latitude: 36.7507347,
|
||||
longitude: -4.4213623,
|
||||
},
|
||||
// Matches the confirmed service area set on Google Business Profile.
|
||||
areaServed: [
|
||||
{ '@type': 'City', name: 'Málaga' },
|
||||
{ '@type': 'City', name: 'Marbella' },
|
||||
{ '@type': 'City', name: 'Benalmádena' },
|
||||
{ '@type': 'City', name: 'Fuengirola' },
|
||||
{ '@type': 'City', name: 'Torremolinos' },
|
||||
{ '@type': 'AdministrativeArea', name: 'Provincia de Málaga' },
|
||||
{ '@type': 'Place', name: 'Costa del Sol' },
|
||||
],
|
||||
contactPoint: {
|
||||
'@type': 'ContactPoint',
|
||||
contactType: 'customer service',
|
||||
@@ -57,7 +100,7 @@ const structuredDataScripts = [
|
||||
? [structuredData]
|
||||
: []) as Array<Record<string, unknown>>),
|
||||
];
|
||||
const umamiHost = Astro.site ? Astro.site.origin : 'https://www.mnqcatering.com';
|
||||
const umamiHost = Astro.site ? Astro.site.origin : 'https://www.mnqeventos.es';
|
||||
const rawUmamiUrl = Astro.url ? import.meta.env.PUBLIC_UMAMI_URL : undefined;
|
||||
const umamiWebsiteId = import.meta.env.PUBLIC_UMAMI_WEBSITE_ID;
|
||||
const normalizedUmamiUrl = rawUmamiUrl
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { defineMiddleware } from 'astro:middleware';
|
||||
|
||||
const CANONICAL_HOST = 'www.mnqeventos.es';
|
||||
|
||||
/**
|
||||
* SEO: enforce the canonical host at the application layer. www.mnqeventos.es
|
||||
* is the real, registered, live production domain. mnqcatering.com was an
|
||||
* intended rename that was never actually registered (confirmed via WHOIS —
|
||||
* no match) — do NOT set CANONICAL_HOST back to it unless that domain is
|
||||
* actually purchased and its DNS points at this server, or this redirect
|
||||
* will send every visitor to a host that doesn't resolve.
|
||||
*
|
||||
* NOTE: this must also be verified at the reverse-proxy/hosting layer — host
|
||||
* detection here depends on how the Host header (or X-Forwarded-Host, when
|
||||
* behind a proxy) actually arrives in production. If the proxy doesn't
|
||||
* forward the original host, this check will see the wrong value.
|
||||
*/
|
||||
export const onRequest = defineMiddleware(async (context, next) => {
|
||||
const requestHost = context.url.host;
|
||||
|
||||
if (requestHost !== CANONICAL_HOST) {
|
||||
const redirectUrl = new URL(context.url.pathname + context.url.search, `https://${CANONICAL_HOST}`);
|
||||
return context.redirect(redirectUrl.toString(), 301);
|
||||
}
|
||||
|
||||
const response = await next();
|
||||
|
||||
// Baseline security headers (flagged missing in the 2026-09-15 SEO/security
|
||||
// audit — no HSTS, X-Content-Type-Options, X-Frame-Options, or
|
||||
// Referrer-Policy were present on any response).
|
||||
//
|
||||
// Deliberately NOT setting Content-Security-Policy here: the site relies on
|
||||
// inline <script> blocks (no nonce/hash setup), inline style="" attributes
|
||||
// throughout every page, and an optional analytics script loaded from a
|
||||
// runtime-configured host (PUBLIC_UMAMI_URL). A CSP tight enough to matter
|
||||
// would need nonces wired through every inline script/style or it will
|
||||
// silently break rendering/interactivity in production. Needs its own pass
|
||||
// with live testing, not a guess baked in here.
|
||||
response.headers.set('Strict-Transport-Security', 'max-age=63072000; includeSubDomains; preload');
|
||||
response.headers.set('X-Content-Type-Options', 'nosniff');
|
||||
response.headers.set('X-Frame-Options', 'DENY');
|
||||
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
|
||||
response.headers.set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
|
||||
|
||||
return response;
|
||||
});
|
||||
@@ -93,11 +93,19 @@ const whatsappAriaLabel = 'Contactar con MNQ por WhatsApp';
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stack-4">
|
||||
<span class="label-caps text-primary">Dirección</span>
|
||||
<div style="display:flex;align-items:center;gap:var(--space-4);">
|
||||
<Icon name="architecture" class="text-primary" />
|
||||
<p class="text-muted">Calle Borde Alegre, 1, 29014, Málaga</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stack-4">
|
||||
<span class="label-caps text-primary">Sitio web</span>
|
||||
<div style="display:flex;align-items:center;gap:var(--space-4);">
|
||||
<Icon name="language" class="text-primary" />
|
||||
<a class="text-muted contact-link" href="https://www.mnqcatering.com">www.mnqcatering.com</a>
|
||||
<a class="text-muted contact-link" href="https://www.mnqeventos.es">www.mnqeventos.es</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -91,8 +91,8 @@ const corporateStructuredData = {
|
||||
<img src="/images/foto-corporativo.jpg" alt="Lanzamientos de marca" style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:center 25%;" width="1264" height="848" loading="lazy" decoding="async" />
|
||||
<div class="bento-card-overlay"></div>
|
||||
<div class="bento-card-body">
|
||||
<h3 class="text-headline-md" style="color:var(--color-on-surface);">Lanzamientos de Marca</h3>
|
||||
<p class="text-muted" style="max-width:28rem;">Impacto visual y conceptual para presentaciones que trascienden.</p>
|
||||
<h3 class="text-headline-md text-white">Lanzamientos de Marca</h3>
|
||||
<p style="color:rgb(255 255 255/80%);max-width:28rem;margin-top:var(--space-2);">Impacto visual y conceptual para presentaciones que trascienden.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -100,8 +100,8 @@ const corporateStructuredData = {
|
||||
<img src="/images/generated/gallery-2.webp" alt="Coffee Breaks Premium" style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:center top;" width="1024" height="1024" loading="lazy" decoding="async" />
|
||||
<div class="bento-card-overlay"></div>
|
||||
<div class="bento-card-body">
|
||||
<h3 class="text-headline-md" style="color:var(--color-on-surface);">Coffee Breaks Premium</h3>
|
||||
<p class="text-muted">Pausas que inspiran productividad y networking de calidad.</p>
|
||||
<h3 class="text-headline-md text-white">Coffee Breaks Premium</h3>
|
||||
<p style="color:rgb(255 255 255/80%);margin-top:var(--space-2);">Pausas que inspiran productividad y networking de calidad.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -109,8 +109,8 @@ const corporateStructuredData = {
|
||||
<img src="/images/foto-bodas.jpg" alt="Galas y Cenas Anuales" style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:center 40%;" width="1264" height="848" loading="lazy" decoding="async" />
|
||||
<div class="bento-card-overlay"></div>
|
||||
<div class="bento-card-body">
|
||||
<h3 class="text-headline-md" style="color:var(--color-on-surface);">Galas & Cenas Anuales</h3>
|
||||
<p class="text-muted" style="max-width:28rem;">Protocolo impecable para los momentos más solemnes de su organización.</p>
|
||||
<h3 class="text-headline-md text-white">Galas & Cenas Anuales</h3>
|
||||
<p style="color:rgb(255 255 255/80%);max-width:28rem;margin-top:var(--space-2);">Protocolo impecable para los momentos más solemnes de su organización.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import MainLayout from '../layouts/MainLayout.astro';
|
||||
<p>
|
||||
<strong>MNQ Catering y Evento</strong><br />
|
||||
Teléfono: <a href="tel:+34678171513">+34 678 17 15 13</a><br />
|
||||
Sitio web: <a href="https://www.mnqcatering.com">www.mnqcatering.com</a>
|
||||
Sitio web: <a href="https://www.mnqeventos.es">www.mnqeventos.es</a>
|
||||
</p>
|
||||
|
||||
<h2>2. Datos que tratamos</h2>
|
||||
|
||||
Reference in New Issue
Block a user