לדלג לתוכן

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 שכוללת:

  1. קומפוננטת React שמודדת את כל המדדים בזמן אמת
  2. שליחת הנתונים לנקודת קצה (API endpoint)
  3. נקודת קצה שמקבלת ומאחסנת את הנתונים
  4. קומפוננטת דשבורד פשוטה שמציגה ממוצעים וגרף של המדדים

השתמשו בספריית 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>
  );
}

שכתבו את הדף כך שכל שלושת המדדים יהיו בטווח הירוק.


שאלות

  1. מה ההבדל בין FID ל-INP? למה Google החליף ביניהם?
  2. מה זה layout shift ומתי הוא לא נחשב ל-CLS? (רמז: user-initiated)
  3. תארו סיטואציה שבה LCP יהיה מעולה אבל חווית המשתמש עדיין גרועה.
  4. מה ההבדל בין Field Data ל-Lab Data? למה שניהם חשובים?
  5. כיצד startTransition של React עוזר לשפר INP?