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.
14 KiB
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.jsonabsent,GOOGLE_API_KEYunset — confirmed viagoogle_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:
<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:
<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 usesrcset. 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 explicitwidth/heightattributes andloading="lazy" decoding="async"; hero images correctly useloading="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) orasync 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,
preconnecthints present for the Google Fonts origins,font-display: swaprequested 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
- Remove
animation-delayfrom the LCP heading (finding 1) — single highest-leverage change, likely worth ~0.5-2s of LCP. - Fix the duplicate render-blocking Google Fonts
<link>(finding 3) — quick, low-risk. - Investigate/fix the
aviso-legal.cssbundling leak (finding 2) — removes 450ms render-blocking + 12KB dead weight sitewide. - Enable HTML compression at the hosting/proxy layer (finding 4).
- 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.
- Complete a Lighthouse run for
/corporativo-businessto close the coverage gap (finding 7).