10.4 Core Web Vitals תרגול
תרגול - מדדי ביצועים - Core Web Vitals¶
תרגיל 1 - שיפור LCP¶
נתון דף בית עם תמונת hero גדולה שגורמת ל-LCP של 5.2 שניות. זהו את הבעיות ותקנו:
// app/page.tsx
'use client';
import { useEffect, useState } from 'react';
export default function HomePage() {
const [heroData, setHeroData] = useState(null);
useEffect(() => {
fetch('/api/hero')
.then(res => res.json())
.then(setHeroData);
}, []);
if (!heroData) return <div>טוען...</div>;
return (
<main>
<section className="hero">
<img src={heroData.image} className="hero-image" />
<h1>{heroData.title}</h1>
<p>{heroData.subtitle}</p>
</section>
<section>
<h2>מוצרים מובילים</h2>
{/* תוכן נוסף */}
</section>
</main>
);
}
.hero {
position: relative;
height: 80vh;
}
.hero-image {
width: 100%;
height: 100%;
object-fit: cover;
}
תקנו את הקוד כך ש-LCP ירד מתחת ל-2.5 שניות.
תרגיל 2 - שיפור INP¶
נתונה קומפוננטת חיפוש שגורמת ל-INP של 800 מ"ש. כל הקלדה גורמת לסינון של 10,000 פריטים ורינדור מחדש:
'use client';
import { useState } from 'react';
interface Item {
id: number;
name: string;
category: string;
description: string;
}
// רשימת 10,000 פריטים
const allItems: Item[] = generateItems(10000);
export default function SearchPage() {
const [query, setQuery] = useState('');
const filteredItems = allItems.filter(item =>
item.name.toLowerCase().includes(query.toLowerCase()) ||
item.description.toLowerCase().includes(query.toLowerCase())
);
return (
<div>
<input
value={query}
onChange={e => setQuery(e.target.value)}
placeholder="חיפוש..."
/>
<p>{filteredItems.length} תוצאות</p>
<ul>
{filteredItems.map(item => (
<li key={item.id}>
<h3>{item.name}</h3>
<p>{item.category}</p>
<p>{item.description}</p>
</li>
))}
</ul>
</div>
);
}
שפרו את הקוד כך ש-INP ירד מתחת ל-200 מ"ש. השתמשו בלפחות שתי טכניקות שונות.
תרגיל 3 - תיקון CLS¶
נתון דף שמקבל ציון CLS של 0.45. זהו את כל הגורמים ל-layout shift ותקנו:
'use client';
import { useEffect, useState } from 'react';
export default function ArticlePage() {
const [ad, setAd] = useState(null);
const [comments, setComments] = useState([]);
useEffect(() => {
// פרסומת נטענת אחרי 2 שניות
setTimeout(() => {
setAd({ image: '/ad-banner.jpg', link: '/promo' });
}, 2000);
// תגובות נטענות אחרי 3 שניות
fetch('/api/comments').then(r => r.json()).then(setComments);
}, []);
return (
<main>
<h1>כותרת המאמר</h1>
{ad && (
<div className="ad-banner">
<img src={ad.image} />
<a href={ad.link}>לפרטים נוספים</a>
</div>
)}
<p>פסקה ראשונה של המאמר...</p>
<p>פסקה שנייה של המאמר...</p>
<img src="/article-image.png" />
<p>פסקה שלישית...</p>
<div className="comments">
<h2>תגובות</h2>
{comments.map(comment => (
<div key={comment.id}>
<strong>{comment.author}</strong>
<p>{comment.text}</p>
</div>
))}
</div>
</main>
);
}
תרגיל 4 - מדידת Web Vitals¶
בנו מערכת מדידה מלאה של Core Web Vitals שכוללת:
- קומפוננטת React שמודדת את כל המדדים בזמן אמת
- שליחת הנתונים לנקודת קצה (API endpoint)
- נקודת קצה שמקבלת ומאחסנת את הנתונים
- קומפוננטת דשבורד פשוטה שמציגה ממוצעים וגרף של המדדים
השתמשו בספריית web-vitals.
תרגיל 5 - ניתוח ביצועים עם Performance API¶
כתבו utility function שמודד ומדווח על ביצועי טעינת הדף באמצעות Performance API המובנה בדפדפן.
הפונקציה צריכה למדוד:
- זמן DNS lookup
- זמן חיבור TCP
- זמן TTFB
- זמן טעינת דף מלא
- רשימת משאבים שלקחו יותר מ-500 מ"ש לטעינה
- כמות ה-layout shifts
תרגיל 6 - אופטימיזציה מקיפה¶
נתון דף מוצר עם הציונים הבאים:
- LCP: 4.8s
- INP: 350ms
- CLS: 0.32
'use client';
import { useEffect, useState } from 'react';
export default function ProductPage({ params }: { params: { id: string } }) {
const [product, setProduct] = useState(null);
const [reviews, setReviews] = useState([]);
const [related, setRelated] = useState([]);
useEffect(() => {
fetch(`/api/products/${params.id}`).then(r => r.json()).then(setProduct);
fetch(`/api/products/${params.id}/reviews`).then(r => r.json()).then(setReviews);
fetch(`/api/products/${params.id}/related`).then(r => r.json()).then(setRelated);
}, [params.id]);
if (!product) return <div>טוען...</div>;
function handleAddToCart() {
// חישוב מחיר עם הנחות מורכב
const finalPrice = calculateComplexPricing(product, reviews.length);
// עדכון כל הקומפוננטות
setProduct({ ...product, inCart: true });
// שליחה לשרת
fetch('/api/cart', { method: 'POST', body: JSON.stringify({ id: product.id, price: finalPrice }) });
}
return (
<div>
<img src={product.image} style={{ width: '100%' }} />
<h1 style={{ fontSize: '14px' }}>{product.name}</h1>
<p>{product.description}</p>
<span>{product.price} ₪</span>
<button onClick={handleAddToCart}>הוסף לסל</button>
<div>
<h2>ביקורות</h2>
{reviews.map(r => (
<div key={r.id}>
<img src={r.authorAvatar} style={{ width: '50px', height: '50px', borderRadius: '50%' }} />
<p>{r.text}</p>
</div>
))}
</div>
<div>
<h2>מוצרים קשורים</h2>
{related.map(r => (
<div key={r.id}>
<img src={r.image} />
<p>{r.name}</p>
</div>
))}
</div>
</div>
);
}
שכתבו את הדף כך שכל שלושת המדדים יהיו בטווח הירוק.
שאלות¶
- מה ההבדל בין FID ל-INP? למה Google החליף ביניהם?
- מה זה layout shift ומתי הוא לא נחשב ל-CLS? (רמז: user-initiated)
- תארו סיטואציה שבה LCP יהיה מעולה אבל חווית המשתמש עדיין גרועה.
- מה ההבדל בין Field Data ל-Lab Data? למה שניהם חשובים?
- כיצד
startTransitionשל React עוזר לשפר INP?