Phase 4 — Core Web Vitals & Measurement
Goal: all indexable page types score "mostly green" on Google's Core Web Vitals thresholds (LCP < 2.5s, INP < 200ms, CLS < 0.1) in lab testing. Search Console and Bing Webmaster Tools are fully operational with baseline data established.
Start condition: Phase 1 deployed to production and verified in Google Search Console (domain verified, sitemap submitted). Ideally, ≥ 28 days of CrUX field data collected before optimisation work begins so you have a real baseline.
0. Prerequisites & ground rules
- This phase is measurement-first. Do not optimise blindly — run the baseline (Task 1) before writing any code. The results dictate which tasks are needed and in what order.
- CWV has three metrics: LCP (Largest Contentful Paint), INP (Interaction to Next Paint), CLS (Cumulative Layout Shift). Prioritise the metric that is furthest from "good" across the most page types.
- Lab testing (PageSpeed Insights) gives fast, reproducible results and is the bar for the Definition of Done. Field data (CrUX in Search Console) is the source of truth Google uses for ranking — it lags lab data by weeks.
- All file paths below are relative to
record-web/.
Task 1 — Baseline measurement (mandatory before any code changes)
Run PageSpeed Insights against the production URL of each representative page type. Use the Mobile tab (Google's primary index is mobile-first).
| Page type | Test URL | LCP | INP | CLS | Verdict |
|---|---|---|---|---|---|
| Home | <prod-url>/ | — | — | — | — |
| Category | <prod-url>/<category-slug> | — | — | — | — |
| Listing | <prod-url>/<listing-slug> | — | — | — | — |
| Movie Detail | <prod-url>/<movie-slug>/<id> | — | — | — | — |
| Show Detail | <prod-url>/<show-slug>/<id> | — | — | — | — |
| Live Detail | <prod-url>/<live-slug>/<id> | — | — | — | — |
Fill in the table and include it in the Phase 4 PR description. Tasks 2–7 should only be executed for page types where a metric is "needs improvement" (amber) or "poor" (red).
Shortcut: if all metrics are already "good" (green) after Phase 1 is live — which is possible given Next.js App Router SSR — you may skip Tasks 2–6 and proceed directly to Task 7 (Search Console monitoring).
Task 2 — Hero image optimisation (LCP)
The LCP element on detail pages is almost always the hero/poster image. Verify this with PageSpeed Insights "Opportunities → Largest Contentful Paint element" section.
2.1 Confirm <Image> is used
All hero images must use next/image (<Image> component). A plain <img> tag does
not benefit from Next.js image optimisation (lazy loading, AVIF/WebP, srcset).
Search for <img tags in detail page components:
grep -rn '<img ' src/views/MovieDetails/ src/views/ShowDetails/ src/components/complex/HeroDetail/
Replace any <img> with <Image> from next/image.
2.2 Add priority prop to the hero image
The hero image is above the fold and is the LCP candidate — it must not be lazy-loaded.
File: src/components/complex/HeroDetail/AppCoreHeroDetail.tsx (or wherever the
hero image renders)
import Image from 'next/image';
// The first / hero image:
<Image
src={heroImageUrl}
alt={item.title}
fill // or explicit width/height
priority // ← disables lazy loading for this image
sizes="(max-width: 768px) 100vw, 50vw"
/>;
priorityshould be set on at most one image per page — the one that is the LCP candidate. Setting it on multiple images wastes bandwidth.
2.3 Confirm remote image domain is allowlisted
Next.js requires remote image hostnames to be declared in next.config.js. The current
config already allows image-proxy.ps.accedo.tv and cdn.one.accedo.tv. If the JWX
thumbnail URL uses a different domain, add it to remotePatterns:
// next.config.js
images: {
remotePatterns: [
{ hostname: 'image-proxy.ps.accedo.tv' },
{ hostname: 'cdn.one.accedo.tv' },
// { hostname: '<jwx-cdn-domain>' }, // add if JWX thumbnails come from a different host
],
},
Task 3 — CLS audit (layout stability)
CLS measures unexpected layout shifts. Run PageSpeed Insights → "Diagnostics → Avoid large layout shifts" to identify the offending elements on each page type.
Common causes in Next.js apps:
3.1 Images without declared dimensions
Any <Image> without explicit width/height (and not using fill) causes a layout
shift while the browser waits for the image to load. Fix: either use fill inside a
sized container, or declare width and height explicitly.
// Causes CLS — dimensions unknown until image loads:
<Image src={url} alt="..." />
// Fixed — container reserves space:
<div style={{ position: 'relative', aspectRatio: '16/9' }}>
<Image src={url} alt="..." fill sizes="..." />
</div>
3.2 Dynamic content inserted above the fold
If a notification banner, cookie bar, or similar element is injected above existing
content after page load, it shifts everything down. Fix: reserve the space in the layout
even when the element is not visible (use visibility: hidden or a placeholder with
the same dimensions).
3.3 Web fonts causing FOUT (Flash of Unstyled Text)
Unstyled text that reflows when the web font loads contributes to CLS.
File: src/app/fontStyles.module.css — confirm font-display: swap or optional
is set for all @font-face declarations:
@font-face {
font-family: 'YourFont';
src: url('/fonts/YourFont.woff2') format('woff2');
font-display: swap; /* ← ensures text is visible during font load */
}
Task 4 — Font loading (LCP + CLS)
Fonts block rendering if not preloaded. Next.js App Router handles Google Fonts
automatically via next/font. If custom fonts are loaded via CSS @font-face, add a
<link rel="preload"> for the WOFF2 file in src/app/layout.tsx:
// In RootLayout:
<html lang="pt-BR">
<head>
<link
rel="preload"
href="/fonts/YourFont.woff2"
as="font"
type="font/woff2"
crossOrigin="anonymous"
/>
</head>
<body>...</body>
</html>
If fonts are already loaded via
next/font/localornext/font/google, this step is handled automatically — skip it.
Task 5 — JavaScript bundle analysis
Large JS bundles delay TTI (Time to Interactive) and can inflate INP. Analyse the bundle once, identify the biggest wins, and address only those that affect indexable pages.
5.1 Enable bundle analyser
pnpm add -D @next/bundle-analyzer
File: next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
});
module.exports = withBundleAnalyzer({
/* existing config */
});
Run:
ANALYZE=true pnpm build
Open the generated report in /.next/analyze/client.html.
5.2 What to look for
| Finding | Fix |
|---|---|
| A large library loaded on every page (e.g. a date library for a detail-only feature) | Lazy-import with dynamic(() => import(...)) |
| The same module bundled twice (two versions of the same package) | Check package.json for version conflicts; deduplicate via pnpm dedupe |
| A heavy analytics or player SDK loaded eagerly | Defer with next/script strategy="lazyOnload" |
Focus only on bundles that are loaded on the indexable page types. Do not optimise pages that are already excluded from crawling.
Task 6 — ISR cache tuning for detail pages
Next.js App Router detail pages currently inherit the root layout's revalidate value
(from env DEFAULT_CACHE_REVALIDATION). For crawlers, a stale cache is fine — the
goal is to serve full HTML fast, not to always be perfectly fresh.
If the current DEFAULT_CACHE_REVALIDATION is very low (< 60s), detail pages may be
generating a new SSR response on every crawl request, which increases server load and
Time to First Byte (TTFB — a contributor to LCP).
Add segment-level revalidate to detail page routes to cache them more aggressively:
File: src/app/[[...routeSegments]]/layout.tsx (or the page file if no segment
layout exists)
// Cache detail page responses for up to 1 hour.
// Content changes are picked up on the next revalidation cycle.
export const revalidate = 3600;
Only do this if the baseline shows TTFB > 600ms on detail pages. If TTFB is already fast, skip — the added complexity is not worth it.
Task 7 — Search Console & Bing Webmaster Tools monitoring
This task starts immediately after Phase 1 is deployed and runs continuously.
7.1 Google Search Console
- Verify domain ownership (Task 8 in Phase 1 wired the HTML tag — click "Verify" in Search Console).
- Submit sitemap: Settings → Sitemaps → Add →
${baseUrl}/sitemap.xml. - After 1–2 weeks, check:
- Coverage report: are all indexable page types being discovered and indexed? Are any showing "Crawled — currently not indexed" (common with thin content) or "Discovered — currently not indexed" (crawl budget issue)?
- Core Web Vitals report: are pages shifting from "Poor" to "Needs improvement" to "Good" as fixes roll out?
- Rich results report (Phase 3): are eligible
VideoObjectwatch-page URLs appearing? Any schema errors?
- After 28+ days of field data: compare the CrUX thresholds in the CWV report against the lab baseline from Task 1. If field data is worse than lab, investigate real-device / real-network conditions (image sizes, JS on mobile).
7.2 Bing Webmaster Tools
- Sign in at webmaster.bing.com with a Microsoft account.
- Add the site and verify ownership using the meta tag set in Phase 1 Task 8.
- Submit the sitemap.
- Use the "Crawl control" slider (available in Bing Webmaster Tools) to manage
Bingbot crawl rate — this replaces the
crawl-delaythat was removed fromrobots.txtin Phase 1.
7.3 Ongoing monitoring — what to watch
| Signal | Tool | Threshold | Action if breached |
|---|---|---|---|
| Indexed pages count | Search Console Coverage | All in-scope pages indexed within 60 days of launch | Investigate Coverage errors; check robots.txt / noindex |
| CWV field LCP | Search Console CWV | < 2.5s ("Good") for ≥ 75% of URLs | Re-run PageSpeed Insights; target the failing page type |
| CWV field CLS | Search Console CWV | < 0.1 ("Good") for ≥ 75% of URLs | Audit for new image / dynamic content layout shifts |
| Rich results errors | Search Console Rich Results | 0 errors on eligible VideoObject pages | Check JSON-LD and watch-page eligibility; a JWX field may be missing |
Verification checklist (Phase 4 done when all pass)
- Baseline measurement table (Task 1) is documented and included in the PR.
- All indexable page types score "good" (green) on LCP, INP, and CLS in PageSpeed Insights (Mobile) lab testing.
- PageSpeed Insights → "Opportunities" has no actionable items above "Low impact" for any indexable page type.
- Google Search Console domain verified, sitemap submitted, no Coverage errors.
- Bing Webmaster Tools domain verified, sitemap submitted.
- If Phase 3 is complete: Search Console Rich Results reports an eligible
VideoObjectwatch page. Validate genericTVSeriesmarkup separately with Schema Markup Validator; Google does not document a standalone TVSeries rich result. -
pnpm buildsucceeds. No TypeScript errors introduced by this phase. -
/code-reviewskill reports no BLOCKERs.