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

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

113 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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).