b084aeff6e
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.
113 lines
14 KiB
Markdown
113 lines
14 KiB
Markdown
# 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).
|