Skip to main content

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 typeTest URLLCPINPCLSVerdict
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"
/>;

priority should 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/local or next/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

FindingFix
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 eagerlyDefer 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

  1. Verify domain ownership (Task 8 in Phase 1 wired the HTML tag — click "Verify" in Search Console).
  2. Submit sitemap: Settings → Sitemaps → Add → ${baseUrl}/sitemap.xml.
  3. 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 VideoObject watch-page URLs appearing? Any schema errors?
  4. 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

  1. Sign in at webmaster.bing.com with a Microsoft account.
  2. Add the site and verify ownership using the meta tag set in Phase 1 Task 8.
  3. Submit the sitemap.
  4. Use the "Crawl control" slider (available in Bing Webmaster Tools) to manage Bingbot crawl rate — this replaces the crawl-delay that was removed from robots.txt in Phase 1.

7.3 Ongoing monitoring — what to watch

SignalToolThresholdAction if breached
Indexed pages countSearch Console CoverageAll in-scope pages indexed within 60 days of launchInvestigate Coverage errors; check robots.txt / noindex
CWV field LCPSearch Console CWV< 2.5s ("Good") for ≥ 75% of URLsRe-run PageSpeed Insights; target the failing page type
CWV field CLSSearch Console CWV< 0.1 ("Good") for ≥ 75% of URLsAudit for new image / dynamic content layout shifts
Rich results errorsSearch Console Rich Results0 errors on eligible VideoObject pagesCheck 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 VideoObject watch page. Validate generic TVSeries markup separately with Schema Markup Validator; Google does not document a standalone TVSeries rich result.
  • pnpm build succeeds. No TypeScript errors introduced by this phase.
  • /code-review skill reports no BLOCKERs.