10.4 Core Web Vitals הרצאה
מדדי ביצועים - Core Web Vitals
בשיעור זה נלמד על מדדי הביצועים שגוגל משתמש בהם כגורמי דירוג. נבין מה כל מדד מודד, מהם הספים לציון טוב, וכיצד לשפר כל מדד.
מה זה Core Web Vitals
- סט של מדדים שגוגל הגדיר כחיוניים לחווית משתמש טובה באינטרנט
- שלושה מדדים עיקריים: LCP, INP, CLS
- נמדדים ממשתמשים אמיתיים בשטח (Field Data) ולא רק בסביבת מעבדה (Lab Data)
- משפיעים על הדירוג בתוצאות החיפוש מאז יוני 2021
LCP - Largest Contentful Paint
- מודד כמה זמן לוקח עד שהאלמנט הגדול ביותר בחלק העליון של הדף (above the fold) מוצג על המסך
- האלמנט יכול להיות: תמונה, בלוק טקסט, וידאו, או אלמנט עם background-image
ספים
| ציון |
ערך |
| טוב |
עד 2.5 שניות |
| צריך שיפור |
2.5 - 4 שניות |
| גרוע |
מעל 4 שניות |
גורמים שפוגעים ב-LCP
- שרת איטי (TTFB גבוה)
- חסימת רינדור על ידי CSS ו-JavaScript
- טעינת משאבים איטית (תמונות גדולות, פונטים)
- רינדור בצד הלקוח (CSR)
כיצד לשפר LCP
// 1. תמונות - שימוש ב-next/image עם priority לתמונת ה-hero
import Image from 'next/image';
function HeroSection() {
return (
<section>
<Image
src="/hero-image.jpg"
alt="תמונת גיבור"
width={1200}
height={600}
priority // טוען מיד, ללא lazy loading
sizes="100vw"
/>
<h1>כותרת ראשית</h1>
</section>
);
}
<!-- 2. Preload למשאבים קריטיים -->
<head>
<!-- טעינה מוקדמת של תמונת ה-hero -->
<link rel="preload" as="image" href="/hero-image.jpg" />
<!-- טעינה מוקדמת של פונט -->
<link
rel="preload"
as="font"
type="font/woff2"
href="/fonts/main.woff2"
crossorigin
/>
<!-- טעינה מוקדמת של CSS קריטי -->
<link rel="preload" as="style" href="/critical.css" />
</head>
// 3. SSR במקום CSR - התוכן מגיע כבר ב-HTML
// Next.js Server Component - ברירת מחדל
export default async function HomePage() {
const data = await fetchHeroData();
return (
<main>
<h1>{data.title}</h1>
<Image src={data.image} alt={data.title} width={1200} height={600} priority />
</main>
);
}
/* 4. CSS קריטי inline - הימנעות מחסימת רינדור */
/* ב-Next.js זה קורה אוטומטית עם CSS Modules */
/* הימנעו מ-@import בתוך CSS - זה יוצר chain of requests */
/* רע: */
@import url('https://fonts.googleapis.com/css2?family=Inter');
/* טוב: השתמשו ב-next/font */
INP - Interaction to Next Paint
- מודד את הזמן מהרגע שהמשתמש מבצע אינטראקציה (קליק, הקשה, לחיצת מקש) עד שהדפדפן מציג עדכון ויזואלי
- החליף את FID (First Input Delay) בשנת 2024
- מודד את כל האינטראקציות במהלך ביקור בדף, לא רק את הראשונה
ספים
| ציון |
ערך |
| טוב |
עד 200 מ"ש |
| צריך שיפור |
200 - 500 מ"ש |
| גרוע |
מעל 500 מ"ש |
גורמים שפוגעים ב-INP
- JavaScript כבד שרץ על ה-main thread
- Event handlers מורכבים
- עדכוני DOM גדולים
- Layout thrashing (קריאה וכתיבה חוזרות לסגנונות)
כיצד לשפר INP
// 1. פיצול עבודה כבדה עם scheduler
function handleClick() {
// רע - חוסם את ה-main thread
const result = heavyComputation(data);
updateUI(result);
}
// טוב - פיצול עם requestIdleCallback או scheduler
function handleClickBetter() {
// עדכון ויזואלי מיידי
setLoading(true);
// עבודה כבדה מחוץ ל-main thread
requestIdleCallback(() => {
const result = heavyComputation(data);
setResult(result);
setLoading(false);
});
}
// 2. שימוש ב-startTransition לעדכונים לא דחופים
import { startTransition, useState } from 'react';
function SearchPage() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
function handleSearch(e: React.ChangeEvent<HTMLInputElement>) {
const value = e.target.value;
// עדכון דחוף - input מיידי
setQuery(value);
// עדכון לא דחוף - תוצאות חיפוש
startTransition(() => {
const filtered = filterItems(value);
setResults(filtered);
});
}
return (
<div>
<input value={query} onChange={handleSearch} />
<ul>
{results.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
</div>
);
}
// 3. דחיית event handlers כבדים עם debounce
import { useDeferredValue, useState } from 'react';
function FilterableList({ items }: { items: Item[] }) {
const [filter, setFilter] = useState('');
const deferredFilter = useDeferredValue(filter);
// הסינון מתבצע רק כשה-deferred value מתעדכן
const filteredItems = items.filter(item =>
item.name.includes(deferredFilter)
);
return (
<div>
<input
value={filter}
onChange={e => setFilter(e.target.value)}
placeholder="חיפוש..."
/>
<ul>
{filteredItems.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
}
// 4. Web Worker לחישובים כבדים
// worker.ts
self.onmessage = (e: MessageEvent) => {
const result = heavyComputation(e.data);
self.postMessage(result);
};
// component.tsx
function DataProcessor() {
const [result, setResult] = useState(null);
function processData(data: number[]) {
const worker = new Worker(new URL('./worker.ts', import.meta.url));
worker.onmessage = (e) => {
setResult(e.data);
worker.terminate();
};
worker.postMessage(data);
}
return <button onClick={() => processData(largeDataSet)}>עבד נתונים</button>;
}
CLS - Cumulative Layout Shift
- מודד את כמות ה-layout shifts הבלתי צפויים שקורים בדף
- layout shift הוא כשאלמנט בדף זז ממקומו בלי שהמשתמש ציפה לכך
- מחושב כ: impact fraction * distance fraction
ספים
| ציון |
ערך |
| טוב |
עד 0.1 |
| צריך שיפור |
0.1 - 0.25 |
| גרוע |
מעל 0.25 |
גורמים שגורמים ל-CLS
- תמונות ללא מימדים מוגדרים
- פרסומות שנטענות בצורה דינמית
- פונטים שגורמים ל-FOUT (Flash of Unstyled Text)
- תוכן שנטען בצורה דינמית ודוחף תוכן קיים
כיצד לשפר CLS
// 1. תמיד להגדיר width ו-height לתמונות
// next/image עושה את זה אוטומטית
<Image
src="/product.jpg"
alt="מוצר"
width={400}
height={300}
/>
// לתמונות רגילות:
<img
src="/product.jpg"
alt="מוצר"
width="400"
height="300"
style={{ aspectRatio: '4/3' }}
/>
/* 2. שמירת מקום לתמונות עם aspect-ratio */
.image-container {
aspect-ratio: 16 / 9;
width: 100%;
background-color: #f3f4f6; /* placeholder */
}
.image-container img {
width: 100%;
height: 100%;
object-fit: cover;
}
// 3. טעינת פונטים ללא FOUT
// ב-Next.js - שימוש ב-next/font
import { Rubik } from 'next/font/google';
const rubik = Rubik({
subsets: ['hebrew', 'latin'],
display: 'swap', // מציג טקסט עם fallback font עד שהפונט נטען
fallback: ['Arial', 'sans-serif'],
});
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="he" dir="rtl" className={rubik.className}>
<body>{children}</body>
</html>
);
}
/* 4. שמירת מקום לתוכן דינמי */
.ad-slot {
min-height: 250px; /* גובה מינימלי לפרסומת */
background-color: #f9fafb;
}
.skeleton {
/* Skeleton loading - placeholder עם אנימציה */
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
border-radius: 4px;
}
@keyframes shimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
// 5. שימוש ב-Suspense עם fallback בגודל נכון
import { Suspense } from 'react';
function ProductPage() {
return (
<main>
<h1>מוצרים</h1>
<Suspense fallback={<ProductListSkeleton />}>
<ProductList />
</Suspense>
</main>
);
}
function ProductListSkeleton() {
return (
<div style={{ minHeight: '600px' }}>
{Array.from({ length: 6 }).map((_, i) => (
<div key={i} className="skeleton" style={{ height: '200px', marginBottom: '16px' }} />
))}
</div>
);
}
מדדים נוספים
TTFB - Time to First Byte
- הזמן מהבקשה הראשונה לשרת עד שהבייט הראשון מגיע
- מושפע מביצועי השרת, CDN, ו-DNS lookup
- ספים: טוב עד 800 מ"ש
// שיפור TTFB עם caching ב-Next.js
export const revalidate = 3600; // רענון כל שעה
// או revalidation ספציפי:
export default async function Page() {
const data = await fetch('https://api.example.com/data', {
next: { revalidate: 60 }, // cache ל-60 שניות
});
return <div>{/* תוכן */}</div>;
}
FCP - First Contentful Paint
- הזמן עד שהאלמנט הראשון (טקסט, תמונה, canvas) מוצג על המסך
- ספים: טוב עד 1.8 שניות
TBT - Total Blocking Time
- סכום הזמן שבו ה-main thread חסום (משימות מעל 50 מ"ש)
- מדד Lab בלבד - לא נמדד ממשתמשים אמיתיים
- מדד מקביל ל-INP בסביבת מעבדה
- ספים: טוב עד 200 מ"ש
מדידה עם ספריית web-vitals
// lib/webVitals.ts
import { onCLS, onINP, onLCP, onFCP, onTTFB } from 'web-vitals';
interface Metric {
name: string;
value: number;
rating: 'good' | 'needs-improvement' | 'poor';
id: string;
}
function sendToAnalytics(metric: Metric) {
// שליחה ל-Google Analytics
if (typeof window.gtag === 'function') {
window.gtag('event', metric.name, {
event_category: 'Web Vitals',
value: Math.round(metric.name === 'CLS' ? metric.value * 1000 : metric.value),
event_label: metric.id,
non_interaction: true,
});
}
// או שליחה לשרת שלנו
fetch('/api/vitals', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: metric.name,
value: metric.value,
rating: metric.rating,
page: window.location.pathname,
timestamp: Date.now(),
}),
});
}
export function reportWebVitals() {
onCLS(sendToAnalytics);
onINP(sendToAnalytics);
onLCP(sendToAnalytics);
onFCP(sendToAnalytics);
onTTFB(sendToAnalytics);
}
// app/components/WebVitalsReporter.tsx
'use client';
import { useEffect } from 'react';
import { reportWebVitals } from '@/lib/webVitals';
export function WebVitalsReporter() {
useEffect(() => {
reportWebVitals();
}, []);
return null;
}
// שימוש ב-layout.tsx
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="he" dir="rtl">
<body>
{children}
<WebVitalsReporter />
</body>
</html>
);
}
שימוש בפאנל
- פתחו DevTools (F12 או Cmd+Option+I)
- עברו ללשונית Performance
- לחצו על כפתור ההקלטה (או Cmd+E)
- בצעו פעולות בדף
- עצרו את ההקלטה
מה לחפש
- Main thread - חפשו "long tasks" (משימות מעל 50 מ"ש, מסומנות באדום)
- Layout Shift - חפשו layout shift events בציר הזמן
- LCP - מסומן בציר הזמן כנקודת ציון
- Network - בדקו משאבים שנטענים לאט
טיפים:
1. הקליטו עם CPU throttling (4x slowdown) כדי לסמלץ מכשיר חלש
2. הקליטו עם Network throttling (Slow 3G) כדי לסמלץ חיבור איטי
3. הסתכלו על Summary tab בתחתית לראות חלוקת זמן
4. השתמשו ב-Bottom-Up tab למצוא פונקציות שצורכות הכי הרבה זמן
1. פתחו DevTools
2. עברו ללשונית Lighthouse
3. בחרו קטגוריות: Performance, Accessibility, Best Practices, SEO
4. בחרו מכשיר: Mobile או Desktop
5. לחצו "Analyze page load"
6. קראו את הדוח ופעלו לפי ההמלצות
סיכום
- Core Web Vitals כוללים שלושה מדדים: LCP, INP, CLS
- LCP מודד זמן עד הצגת האלמנט הגדול ביותר - יעד: מתחת ל-2.5 שניות
- INP מודד תגובתיות לאינטראקציות - יעד: מתחת ל-200 מ"ש
- CLS מודד יציבות ויזואלית - יעד: מתחת ל-0.1
- מדדים נוספים: TTFB, FCP, TBT
- ספריית web-vitals מאפשרת מדידה בזמן אמת מהמשתמשים
- Chrome DevTools Performance Panel הוא כלי חיוני לאבחון בעיות ביצועים
- שיפור Core Web Vitals משפר גם את חווית המשתמש וגם את דירוג ה-SEO