Core Web Vitals 2026 — מדריך פרקטי להגיע לציון מושלם
Core Web Vitals 2026 בקוד אמיתי — LCP, INP, CLS, TTFB: מה הסף בכל מטריקה, מה Google מודד בפועל ב-CrUX, ואיך מתקנים כל אחת שורה אחר שורה
למי זה מתאים: מפתחים ובעלי אתרים שראו פעם את Lighthouse מציג מספר אדום ולא בדיוק הבינו למה. המדריך לא ייתן לך עוד "השתמש ב-CDN" — בכל סעיף יש את שורת הקוד או הגדרת ה-build שאתה יכול לעשות עכשיו. כל המספרים כאן מעודכנים ל-2026 (אחרי כניסת INP, אחרי Lighthouse v12, אחרי AVIF ברוב הדפדפנים).
למה זה חשוב — מעבר לציון יפה
לפני שאנחנו צוללים פנימה, חשוב להבין שזה לא תרגיל אקדמי. Core Web Vitals הם אות דירוג רשמי של Google בחיפוש מאז יוני 2021, ומאז מרץ 2024 INP החליף את FID כמטריקה השלישית. אם האתר שלך נופל מתחת ל"Good" באחת מהשלוש — אתה משלם על זה במיקום, גם אם התוכן שלך מצוין.
אבל אפילו אם SEO לא מעניין אותך — המספרים של ה-Conversion מדברים בעד עצמם:
- כל שנייה נוספת ב-LCP חותכת בערך 10% מה-Conversion ב-e-commerce.
- 53% מהמשתמשים במובייל נוטשים אתר שלא נטען תוך 3 שניות.
- אתרים שעוברים את כל שלוש המטריקות מקבלים בממוצע 24% פחות נטישות לפי CrUX.
ויש גם משהו ש-אנליטיקס לא תופס: המותג שלך. אתר שמהבהב, קופץ, ולא מגיב — נראה זול בלי קשר לעיצוב. אתר שמופיע מיד וחלק נראה יקר. זה החלק ש-Google לא מדרג, אבל המשתמשים שלך כן.
ה-2026 העלתה את הציפיות. לפי נתוני CrUX מ-מאי 2026 רק 55.9% מהדומיינים בעולם עוברים את כל שלוש המטריקות יחד (LCP טוב ב-68.6%, CLS ב-81.3%, INP ב-86.6%) — כלומר כמעט מחצית מהאתרים עדיין נכשלים בהערכה המלאה. אם פעם 3 שניות LCP נחשב סביר — היום זה גורם להרגשה של אתר מיושן.
זה גם המקום שבו Core Web Vitals נפגש עם שאר עבודת ה-SEO הטכני. אם אתה אתר React שמרונדר בצד-הלקוח, המהירות לבדה לא תספיק — קרא את render שמנוע החיפוש בכלל רואה את התוכן ואת התשתית של Next.js 16 שפותרת את שתי הבעיות בו-זמנית.
המטריקות של 2026
יש שלוש מטריקות-ליבה רשמיות (Core Web Vitals) ועוד אחת חשובה שמשלימה את התמונה (TTFB). תכיר את כולן.
LCP — Largest Contentful Paint
הזמן עד שמופיע על המסך האלמנט הגדול ביותר שגלוי מעל הקיפול (Above the fold). בדרך כלל זו תמונת Hero, כותרת ראשית, או בלוק טקסט גדול. זו המטריקה שעונה על השאלה "מתי המשתמש רואה משהו אמיתי".
INP — Interaction to Next Paint
החליפה רשמית את FID ב-12 במרץ 2024. INP מודדת את ההשהיה בין כל אינטראקציה (לחיצה, הקלדה, מגע) לבין הציור הבא של הדפדפן, ומחזירה את האינטראקציה הגרועה ביותר בכל הסשן (לא רק את הראשונה כמו FID). זו מטריקה הרבה יותר אכזרית — היא תפסה JS איטי שהיה מסתתר.
נתון מ-2026: לפי CrUX של מאי 2026 כ-86.6% מהדומיינים בעולם עוברים INP טוב — זו המטריקה עם שיעור המעבר הגבוה ביותר מהשלוש, אבל זה בדיוק מה שמטעה. INP היא המטריקה שהכי קל לקבל בה ציון Lab ירוק ולהיכשל בה בשטח, כי Lab בכלל לא מודד אותה (ראה למטה). מפתחים רואים Lighthouse 98 ומניחים שהכל טוב, בעוד שעל מובייל בינוני אמיתי המשתמש סופג 300ms של תקיעה בכל לחיצה. כשאתה כן נכשל ב-INP, כמעט תמיד תגלה את זה רק ב-CrUX, אחרי שכבר עלית לפרודקשן.
CLS — Cumulative Layout Shift
סכום של תזוזות פתאומיות בעמוד. אם הכפתור קופץ למטה רגע לפני שלחצת — זה CLS. נמדד כמכפלה של שטח התזוזה ומרחק התזוזה, ללא יחידות.
TTFB — Time to First Byte
לא Core Web Vital רשמית, אבל היא תקרת זכוכית של LCP. אם השרת מחזיר את ה-HTML הראשון תוך 2 שניות, אין מצב שתעבור LCP טוב בכלל. תמיד התחל מ-TTFB.
טבלת ספים מלאה — 2026
הסף נמדד באחוזון 75 של הביקורים האמיתיים שלך ב-CrUX (לא אצלך במחשב). כלומר ל-75% מהמשתמשים חייב להיות "Good" כדי שהדף יעבור.
| מטריקה | Good | Needs Improvement | Poor |
|---|---|---|---|
| LCP | ≤ 2.5 שניות | 2.5–4.0 שניות | > 4.0 שניות |
| INP | ≤ 200 מילישניות | 200–500 ms | > 500 ms |
| CLS | ≤ 0.1 | 0.1–0.25 | > 0.25 |
| TTFB | ≤ 800 ms | 800–1800 ms | > 1800 ms |
| FCP | ≤ 1.8 שניות | 1.8–3.0 שניות | > 3.0 שניות |
שים לב — Lighthouse לא מודד INP. INP היא מטריקת שטח (field) בלבד, כי אי אפשר לסמלץ אינטראקציה אמיתית בלי משתמש אמיתי. Lighthouse נותן במקום זה TBT (Total Blocking Time) שמתואם חזק עם INP. אם TBT אצלך מתחת ל-200ms — סביר ש-INP יהיה טוב.
משקלי הציון של Lighthouse v12
ציון ה-Performance של Lighthouse בנוי מחמש מטריקות במשקולות הבאות:
| מטריקה | משקל |
|---|---|
| TBT (Total Blocking Time) | 30% |
| LCP | 25% |
| CLS | 25% |
| FCP | 10% |
| Speed Index | 10% |
הציון בעצם לא ישיר ב-INP אלא ב-TBT שמייצג אותו. לכן יכול להיות שתקבל 95 ב-Lighthouse אבל תיכשל ב-CrUX — אם המשתמשים האמיתיים שלך עם מובייל חלש מקבלים אינטראקציות איטיות שהבדיקה ב-Lab לא הבחינה בהן.
איך מודדים — Lab מול Field
זה הבלבול הכי גדול בעולם הביצועים. צריך להבין את ההבדל לפני שמסתכלים על מספר.
Lab data (סינתטי)
נתון שנמדד במכונה אחת, בתנאי בדיקה קבועים. Lighthouse הוא Lab. נותן לך תוצאה עקבית, חוזרת על עצמה, וטובה לחקירת באג ספציפי. אבל לא משקף את המציאות של 100,000 משתמשים שלך עם 50 מכשירים שונים.
טיפ: Lighthouse משתמש בברירת מחדל ב-"Slow 4G" עם CPU throttling פי 4. זו סימולציה של מובייל בינוני בהודו או אינדונזיה. אם זה נראה לך מחמיר — כך זה. זו המציאות של חצי מהאינטרנט.
Field data (אמיתי)
נתון מ-משתמשים אמיתיים שמתאסף ב-Chrome User Experience Report (CrUX) על פני 28 ימים אחורה. זה מה ש-Google משתמש בו לדירוג. נגיש דרך:
- PageSpeed Insights —
pagespeed.web.dev/ - Search Console → "ניסיון בדף" → "Core Web Vitals"
- CrUX API — לסקריפט שלך
- RUM משלך דרך
web-vitals.jsששולח לאנליטיקס
npm install web-vitals
import { onLCP, onINP, onCLS, onTTFB, onFCP } from 'web-vitals';
function send(metric) {
// שלח לאיפה שאתה רוצה — GA4, Plausible, endpoint משלך
navigator.sendBeacon('/vitals', JSON.stringify(metric));
}
onLCP(send);
onINP(send);
onCLS(send);
onTTFB(send);
onFCP(send);
ספריית web-vitals ב-v4 היא הדרך הרשמית למדידה בקוד. השם של הפונקציה onINP ולא getINP כי הערך הסופי מגיע רק כשהמשתמש עוזב את הדף.
איזה כלי מתי?
- לפיתוח יומיומי — Lighthouse ב-DevTools. מהיר, מקומי, אבל זוכר: זה Lab.
- לפני deploy — PageSpeed Insights על הסטייג׳ינג. נותן גם Lab וגם תחזית של Field.
- בפרודקשן — RUM אמיתי. בלי זה אתה עיוור למה שקורה אצל המשתמשים.
- ל-debug ספציפי — DebugBear / Calibre / SpeedCurve. בתשלום, אבל שווה.
LCP — איך מתקנים
הצעד הראשון הוא למצוא את ה-LCP element. רוב המפתחים מסתכלים על הציון, מנסים פתרונות גנריים, ולא מבינים למה לא משתנה. תפסיק לנחש.
שלב 1: מצא את האלמנט
ב-Chrome DevTools → לשונית Performance → לחץ Record → רענן את הדף → עצור. בסקציית "Web Vitals" תראה את ה-LCP מסומן בריבוע צהוב על הצילום של המסך.
או דרך ה-Console:
new PerformanceObserver((list) => {
const entries = list.getEntries();
const lcp = entries[entries.length - 1];
console.log('LCP element:', lcp.element, 'time:', lcp.renderTime || lcp.loadTime);
}).observe({ type: 'largest-contentful-paint', buffered: true });
ב-95% מהמקרים זו תמונת Hero. ב-5% הנותרים זה בלוק טקסט גדול או וידאו.
שלב 2: Preload לתמונת LCP
הדפדפן מגלה תמונות רק אחרי שהוא קורא את ה-HTML עד לטאג של ה-img. בינתיים שתי-שלוש שניות יורדות. תקדים את הדפדפן:
<link rel="preload" as="image" href="/hero.avif"
fetchpriority="high" type="image/avif">
או בקוד תוסיף fetchpriority="high" ישירות על האלמנט:
<img src="/hero.avif" alt="..." fetchpriority="high" loading="eager"
width="1600" height="900">
אל תוסיף loading="lazy" על LCP element. זה הטעות הכי נפוצה. lazy על תמונה מתחת לקיפול = מצוין; lazy על Hero = הרס LCP.
שלב 3: פורמט תמונה נכון
AVIF נותן 20-25% פחות גודל מ-WebP באותה איכות, ויש לו תמיכה של 94.9% בדפדפנים ב-2026. WebP מגיע ל-96.4%. ההגיון בפרודקשן הוא לשלוח את שניהם:
<picture>
<source srcset="/hero.avif" type="image/avif">
<source srcset="/hero.webp" type="image/webp">
<img src="/hero.jpg" alt="..." width="1600" height="900" fetchpriority="high">
</picture>
שלב 4: Responsive srcset
תפסיק לשלוח תמונה של 2400px ל-iPhone של 390px. תן לדפדפן לבחור:
<img src="/hero-800.avif"
srcset="/hero-400.avif 400w, /hero-800.avif 800w, /hero-1600.avif 1600w"
sizes="(max-width: 768px) 100vw, 50vw"
alt="..." fetchpriority="high" width="1600" height="900">
עם Vite אפשר עם plugin: vite-imagetools יוצר את כל הגדלים והפורמטים אוטומטית בזמן build:
import hero from './hero.jpg?w=400;800;1600&format=avif;webp;jpg&as=picture';
שלב 5: זמן תגובת השרת (TTFB)
אם השרת מחזיר את ה-HTML אחרי 1.5 שניות, אין סיכוי ל-LCP טוב. ראה את הסעיף הייעודי של TTFB למטה.
שלב 6: הסר Render-Blocking
CSS חיצוני חוסם את כל הציור. שתי גישות:
- Critical CSS inline — הוצא את ה-CSS שצריך לרינדור Above-the-Fold והכנס אותו ב-
<style>ב-head. את השאר טען אסינכרונית. - Async load ל-CSS לא קריטי:
<link rel="preload" href="/non-critical.css" as="style"
onload="this.onload=null;this.rel='stylesheet'">
לבילד Vite — plugin vite-plugin-critical עושה את זה אוטומטית. שמור את ה-Critical CSS מתחת ל-14KB כדי שיכנס לחבילה ראשונה של TCP.
שלב 7: פונטים שחוסמים את LCP
אם ה-LCP element הוא טקסט עם פונט מותאם — הטקסט לא יצויר עד שהפונט יורד. עבור לסעיף הפונטים למטה.
INP — איך מתקנים
זו המטריקה הכי קשה ב-2026. אתה לא תזהה INP גרוע באמולטור Lighthouse — צריך לראות אותו אצל המשתמשים. הסיבות הנפוצות:
- JavaScript ארוך שחוסם את ה-Main Thread — חישוב, סינון, רינדור של רשימה גדולה.
- Event handler כבד —
onClickשעושהsetStateשמרנדר 1000 רכיבים. - Third-party — סקריפט אנליטיקס שתופס את הה-CPU בדיוק כשמשתמש לוחץ.
- Hydration ב-SSR — בריאקט/Next, ה-JS שמחבר מחדש את האלמנטים האינטראקטיביים בא בגל אחד גדול.
שלב 1: מצא את ה-Long Tasks
ב-DevTools → Performance → Record אינטראקציה (לחיצה על כפתור). תראה משימות אדומות (Long Task = מעל 50ms). כל אחת מהן חוסמת אינטראקציות.
הבעיה: ב-DevTools אתה משחזר אינטראקציה אחת על המחשב החזק שלך, אבל ה-INP הגרוע קורה אצל משתמש על מובייל ישן שאתה לא רואה. הדרך הוודאית לתפוס את האשם היא web-vitals/attribution בפרודקשן — הוא מחזיר לך בדיוק איזה אלמנט נלחץ ואיזה שלב (input delay, processing, presentation) אכל את הזמן:
import { onINP } from 'web-vitals/attribution';
onINP(({ value, attribution }) => {
console.log('INP:', value, 'ms');
console.log('אלמנט:', attribution.interactionTarget);
console.log('שלב כבד:', attribution.inputDelay, attribution.processingDuration);
}, { reportAllChanges: true });
עכשיו במקום לנחש, אתה מקבל מהשטח את ה-selector המדויק של הכפתור שתוקע אינטראקציות. ברוב המקרים תגלה שזה event handler אחד עם setState כבד, או third-party שתפס את ה-thread.
שלב 2: שבור Long Tasks עם scheduler.yield()
ה-API הזה (זמין ב-Chrome מ-2024 ו-בפיירפוקס מאז אוגוסט 2025; Safari עדיין לא תומך, ולכן זה לא נחשב Baseline — תמיד עם פוליפיל) מאפשר לעצור באמצע משימה ולתת לדפדפן לטפל באינטראקציות:
async function processItems(items) {
for (const item of items) {
process(item);
// נותן לדפדפן הזדמנות לרנדר את האינטראקציה האחרונה
if ('scheduler' in window && 'yield' in scheduler) {
await scheduler.yield();
} else {
await new Promise(r => setTimeout(r, 0));
}
}
}
יתרון על requestIdleCallback: scheduler.yield() ממשיך מיד אחרי שהדפדפן טיפל באינטראקציה, בעדיפות גבוהה. requestIdleCallback יכול לא לחזור לעולם אם ה-thread עמוס. הנה ההבדל המעשי בין דרכי ה-yielding שתפגוש:
| שיטה | מה קורה אחרי ה-yield | בעיה |
|---|---|---|
setTimeout(fn, 0) | חוזר בסוף התור — אחרי כל המשימות שכבר ממתינות | יכול להמתין הרבה אם התור עמוס |
requestIdleCallback | חוזר רק כש-ה-thread פנוי | על דף עמוס עלול לא לרוץ כלל |
await scheduler.yield() | חוזר מיד אחרי שהדפדפן צייר את האינטראקציה, בראש התור | אין — זו הדרך הנכונה (עם פוליפיל ל-Safari) |
הכלל: אם אתה מפצל לולאה כדי להציל INP — scheduler.yield(). אם אתה דוחה עבודה לא דחופה לגמרי (טעינת אנליטיקס) — requestIdleCallback עדיין מתאים.
לפוליפיל:
npm install scheduler-polyfill
import 'scheduler-polyfill';
// עכשיו scheduler.yield() עובד בכל הדפדפנים
שלב 3: Debounce על קלט מתמשך
חיפוש שמרנדר 1000 תוצאות בכל קליק היא הקרבה ל-INP:
import { useDeferredValue, useState } from 'react';
function Search() {
const [query, setQuery] = useState('');
const deferred = useDeferredValue(query); // React מטפל בעדיפות
return (
<>
<input value={query} onChange={e => setQuery(e.target.value)} />
<Results query={deferred} />
</>
);
}
ב-React 18+ זה useDeferredValue ו-useTransition. בלי React — lodash.debounce עם 150ms.
שלב 4: וירטואליזציה של רשימות
<ul> עם 5,000 פריטים = INP פצצה. השתמש ב-@tanstack/react-virtual או react-window:
npm install @tanstack/react-virtual
import { useVirtualizer } from '@tanstack/react-virtual';
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
});
רק הפריטים הגלויים מצוירים. אינטראקציה הופכת ממיידית גם על מובייל.
שלב 5: דחה Hydration ב-React
ב-React 18+ עם Next.js / Remix — השתמש ב-Server Components לכל מה שאינו אינטראקטיבי. רכיב שכולו טקסט סטטי לא צריך JS בכלל. מה שיורד פחות JS = פחות hydration = INP טוב יותר.
שלב 6: third-party
ראה את הסעיף הייעודי למטה. אם Intercom רץ ב-Main thread בשנייה הראשונה — אין מה לעשות עם הקוד שלך.
CLS — איך מתקנים
CLS היא הקלה ביותר לתקן ויחסית קלה למדוד. הסיבות:
1. תמונות בלי מימדים
הכי נפוץ. תמונה בלי width ו-height תופסת 0 פיקסלים, ואז כשהיא נטענת — כל מה שמתחת קופץ.
<!-- רע -->
<img src="/photo.jpg" alt="...">
<!-- טוב -->
<img src="/photo.jpg" alt="..." width="800" height="600">
חשוב להבין: ה-width="800" הוא יחס ליצירת aspect-ratio, לא הגודל בפועל. ה-CSS עדיין יכול לעשות width: 100%; height: auto;. הדפדפן יחשב את האזור הנכון לפני שהתמונה הגיעה.
ל-<picture> המודרני אפשר גם:
img { aspect-ratio: 16 / 9; width: 100%; height: auto; }
2. פונטים — FOIT/FOUT
כשפונט מותאם מחליף פונט-fallback, הטקסט מקבל גודל אחר וכל הדף קופץ. הפתרון: font-display: optional או fallback מותאם בגודל.
CSS גודל-fallback (היה לפני זמן בשם size-adjust):
@font-face {
font-family: 'Heebo';
src: url('/heebo.woff2') format('woff2');
font-display: swap;
size-adjust: 95%; /* התאמה כך שגודל הטקסט יהיה זהה ל-fallback */
ascent-override: 90%;
descent-override: 22%;
}
ב-Next.js עם next/font זה אוטומטי.
3. תוכן מוכנס דינמית מעל הקיפול
באנר, banner cookie, "הסכם לתקנון" שנכנס למעלה אחרי טעינה — דוחק את כל הדף למטה. שלוש אפשרויות:
- שמור מקום מראש (
min-heightעל הקונטיינר). - הכנס מתחת לקיפול (fixed bottom).
- אל תכנס דינמית — תרנדר ב-SSR עם ידיעה אם להציג.
4. ads ו-iframes
תן min-height קבוע למיכל המודעה:
.ad-slot { min-height: 250px; }
עדיף שייראה ריק מאשר שיקפיץ את הדף.
5. אנימציות שזזות layout
top, left, width, height — גורמים reflow. השתמש ב-transform ו-opacity בלבד לאנימציות:
/* רע */
.box:hover { left: 10px; }
/* טוב */
.box:hover { transform: translateX(10px); }
transform רץ ב-Compositor thread ולא משפיע על CLS בכלל.
TTFB — איך מתקנים
זו הקרקע של LCP. תרצה אותו מתחת ל-800ms — רצוי מתחת ל-200ms אם אפשר.
1. שים CDN לפני הכל
אם השרת שלך בוורג׳יניה והמשתמש בתל-אביב — יש 200ms של RTT לפני שמתחילים. Cloudflare / Vercel / Netlify עוטפים את האתר ב-CDN גלובלי. אפילו לאתר סטטי — חובה. אם אתה עדיין מתלבט איפה לארח, ההשוואה ב-Vercel מול Firebase Hosting פורסת בדיוק את ההבדל ב-TTFB בין השניים ומתי כל אחד מנצח.
2. Static Generation במקום Server Render
אם התוכן לא משתנה לכל משתמש — תהפוך אותו לסטטי. Next.js עם generateStaticParams, Astro, Hugo, Eleventy — כולם מייצרים HTML פעם אחת ב-build ומשרתים אותו מהקצה.
// Next.js App Router
export async function generateStaticParams() {
return posts.map(post => ({ slug: post.slug }));
}
TTFB מ-CDN של HTML סטטי = 30-80ms ברוב המקומות בעולם.
3. Edge Functions ל-dynamic
אם אתה חייב לוגיקה צד-שרת — תרוץ ב-Edge. Vercel Edge Functions / Cloudflare Workers נטענים מ-300 מיקומים גלובלית.
// Vercel
export const config = { runtime: 'edge' };
export default function handler(req) {
return new Response('שלום');
}
Cold start של Edge הוא 5-15ms, לעומת Node serverless שיכול להגיע ל-300-1000ms.
4. Vercel Fluid Compute
מאז 2025 Fluid Compute הוא ברירת המחדל לפרויקטים חדשים ב-Vercel: instance בודד של פונקציה מטפל בכמה בקשות במקביל, במקום ליצור instance חדש לכל בקשה. לפי Vercel זה מבטל cold start ב-99.37% מהבקשות. אם הפרויקט שלך ישן יותר, ודא שזה דלוק ב-Project Settings → Functions → Fluid Compute.
5. Database — אל תחנוק את TTFB
קריאה ל-DB לפני שמחזירים את ה-HTML היא הרוצח הנפוץ ביותר. שתי גישות:
- Cache — Redis / Upstash / Vercel KV. החזק את ה-payload המקובע ב-RAM קרוב לקצה.
- Stream HTML — שלח את ה-
<head>ואת ה-Shell של הדף מיד, ותן ל-React לחכות ל-DB עם<Suspense>. TTFB נמדד לפי הבייט הראשון — אם שלחת את ה-<!doctype html>תוך 50ms, סיימת.
תמונות ב-2026 — המדריך המקוצר
בחירת פורמט
| פורמט | תמיכה ב-2026 | יתרון | חיסרון |
|---|---|---|---|
| AVIF | 94.9% | הכי דחוס (20-25% פחות מ-WebP) | מקודד איטי (פי 5-10 מ-JPEG) |
| WebP | 96.4% | מהיר לקודד ולדקודד | גדול יותר מ-AVIF |
| JPEG | 100% | התואמות מלאה | פי 2 גדול מ-WebP |
| PNG | 100% | שקיפות + lossless | רק לאיקונים/לוגואים |
| SVG | 100% | וקטור, סקלאבילי | רק לאיקונים/לוגואים |
ההמלצה ל-2026: קודד AVIF + WebP, fallback ל-JPEG. השתמש ב-<picture> כפי שראינו למעלה.
Sharp ב-build
לא משנה איזה פריימוורק — sharp הוא הסטנדרט לעיבוד תמונות. ב-build הוא יוצר את כל הוריאנטים פעם אחת:
npm install sharp
import sharp from 'sharp';
await sharp('hero.jpg')
.resize(1600)
.avif({ quality: 50 })
.toFile('hero.avif');
Next/Image ו-Vercel Image Optimization
אם אתה ב-Next.js — next/image מטפל בכל זה אוטומטית. הוא יוצר AVIF/WebP על-המקום ב-edge, מוסיף width/height ל-CLS, ומוסיף loading="lazy" (חוץ מ-priority ל-LCP).
import Image from 'next/image';
<Image src="/hero.jpg" alt="..." width={1600} height={900} priority />
החל מ-2025 Vercel מציעים את Image Optimization בכל התוכניות, כולל Hobby. השימוש נספר ב-"Optimized Images" ולא בתעבורה. אם האתר שלך עברית-first ו-RTL, שים לב שהתאמת התמונות והפריסה צריכה להתנהג נכון משני הכיוונים — נגישות ו-RTL בעברית מכסה את הפינות שנשברות דווקא בתמונות ובאלמנטים שצפים.
Lazy Loading
לכל תמונה שלא נראית מיד:
<img src="..." loading="lazy" decoding="async" alt="...">
חוץ מ-LCP. אל תוסיף lazy על Hero — זה הרס.
פונטים — איך לא להרוס LCP
Self-host או Google Fonts?
ב-2026 ההמלצה היא תמיד self-host, אם לא מצב מיוחד. הסיבות:
- הסרת DNS lookup ל-
fonts.googleapis.comו-fonts.gstatic.com(חוסכים 100-300ms). - ה-Cache Policy שלך, לא של Google.
- אין preconnect שלישי שצריך לקנפג.
עם Next.js — next/font/google עושה self-host אוטומטית ב-build, גם אם אתה מגדיר אותו כ-Google Fonts:
import { Heebo } from 'next/font/google';
const heebo = Heebo({ subsets: ['hebrew'], display: 'swap' });
ב-Vite — תוריד ידנית או עם @fontsource/heebo:
npm install @fontsource/heebo
import '@fontsource/heebo/400.css';
import '@fontsource/heebo/700.css';
font-display
| ערך | מה זה עושה | מתי |
|---|---|---|
swap | הצג fallback מיד, החלף כשהפונט הגיע | רוב המקרים |
optional | אם הפונט לא הגיע תוך 100ms — תוותר. הטוב ביותר ל-CLS | רק 0.5% מהאתרים משתמשים בזה — תהיה ב-0.5% |
block | החזק טקסט בלתי-נראה עד 3 שניות | אם הפונט קריטי לזהות המותג |
fallback | פשרה בין block ל-swap | נדיר |
@font-face {
font-family: 'Heebo';
src: url('/heebo.woff2') format('woff2');
font-display: swap; /* או optional לפחות CLS */
}
Preload לפונט קריטי
רק אם הוא חלק מה-LCP element:
<link rel="preload" href="/heebo-700.woff2" as="font"
type="font/woff2" crossorigin>
אל תוסיף preload לכל הפונטים — זה אנטי-תבנית. רק לזה שמופיע ב-Hero.
Subsetting
קובץ heebo.woff2 מלא = 80KB. עם רק התווים העבריים = 25KB. עם Latin בלבד = 15KB.
ב-next/font/google זה אוטומטי דרך subsets: ['hebrew']. ב-self-host — תרץ:
npx fonttools subset heebo.woff2 --unicodes="U+0590-05FF" \
--output-file=heebo-hebrew.woff2
WOFF2 בלבד
ב-2026 אין סיבה לתמוך ב-WOFF, TTF, או EOT. WOFF2 נתמך ב-97%+ של הדפדפנים, ודחוס יותר ב-30%.
JavaScript ו-Third Party
עיקרון: כל JS חיצוני הוא חוב
כל סקריפט שלא כתבת אתה הוא סיכון לביצועים. אנליטיקס, צ׳אט, A/B testing, hotjar, intercom — כולם אכלו אתרים שלמים. לפני שאתה מוסיף עוד סקריפט, שווה לבחור כלי מדידה קליל מלכתחילה — GA4 מול Plausible מול PostHog משווה את משקל הסקריפט של כל אחד, וזה משפיע ישירות על INP.
דחה אנליטיקס
ברוב המקרים אנליטיקס לא חייב להיטען ב-<head>. שים אותו אחרי load:
window.addEventListener('load', () => {
// טעינה אחרי שכל הדף נטען
const s = document.createElement('script');
s.src = 'https://plausible.io/js/script.js';
s.async = true;
document.head.appendChild(s);
});
או עם requestIdleCallback:
requestIdleCallback(() => loadAnalytics(), { timeout: 5000 });
Intercom / צ׳אט — lazy load
אל תטען Intercom עד שהמשתמש גלל או עבר זמן:
let loaded = false;
function loadIntercom() {
if (loaded) return;
loaded = true;
window.Intercom('boot', { app_id: 'xxx' });
}
// טען אחרי 5 שניות או בגלילה ראשונה
setTimeout(loadIntercom, 5000);
window.addEventListener('scroll', loadIntercom, { once: true });
Partytown ל-third-party כבד
הרעיון: להריץ Google Tag Manager, Facebook Pixel, Hotjar — בתוך Web Worker, לא ב-Main Thread. זה אומר שאינטראקציות (INP) לא נחסמות בכלל.
npm install @builder.io/partytown
<script src="/~partytown/partytown.js"></script>
<script type="text/partytown"
src="https://www.googletagmanager.com/gtag/js?id=G-XXX">
</script>
ב-Astro / Next.js יש אינטגרציות רשמיות. הגישה הזו לא מושלמת — Partytown עדיין ב-beta, ולא כל סקריפט עובד בה (כל מה שצריך גישה ל-DOM סינכרונית בעייתי) — אבל לסקריפטים שמסתפקים ב-fetch + cookies זה משחק את ה-INP.
async ו-defer
עקרון בסיסי שאנשים עדיין שוכחים:
<script src="..."></script> <!-- חוסם render -->
<script src="..." async></script> <!-- מוריד במקביל, מבצע מיד כשמגיע -->
<script src="..." defer></script> <!-- מוריד במקביל, מבצע אחרי HTML -->
לסקריפטים שלא תלויים זה בזה — async. לסקריפטים שצריכים סדר ביצוע — defer.
CSS
Critical CSS inline
יש להוציא את ה-CSS שמרנדר את ה-Above-the-Fold ולשים אותו inline בתוך <style> ב-head. כך הדפדפן מתחיל לצייר בלי לחכות לקובץ CSS חיצוני.
עם Vite — vite-plugin-critical:
import { critical } from 'vite-plugin-critical';
export default {
plugins: [critical({
inline: true,
extract: false,
width: 1280,
height: 720,
})],
};
כלל אצבע: מתחת ל-14KB דחוס. מעל זה — אתה חורג מחבילה אחת של TCP ומשלם RTT נוסף.
content-visibility: auto
לסקציות מתחת לקיפול — אמור לדפדפן "אל תחשב את ה-layout שלהן עד שהמשתמש גולל לכאן":
.section { content-visibility: auto; contain-intrinsic-size: 1000px; }
זה יכול לחתוך 30-50% מזמן הרינדור על דפים ארוכים. contain-intrinsic-size חשוב — בלעדיו הדפדפן לא יודע איזה גובה לתפוס לפני שזה נכנס ל-viewport, וזה גורם ל-CLS בגלילה.
הסר CSS לא בשימוש
PurgeCSS או Tailwind CSS עם הגדרת content נכונה. כלל אצבע: אחרי build, CSS לא אמור להיות מעל 50KB. אם אצלך הוא 300KB — יש לך הרבה לא בשימוש.
תקציבי ביצועים — שלא ייפול שוב
הבעיה הכי מתסכלת בביצועים: אתה מתקן הכל, מגיע ל-95, וחצי שנה אחר כך מישהו מוסיף ספריית קרוסלה של 200KB וה-LCP חוזר ל-4 שניות. הדרך היחידה למנוע נסיגה היא תקציב שנכשל ב-CI, לא בעין שלך.
Lighthouse CI עם ספים
@lhci/cli רץ ב-GitHub Actions על כל PR ונכשל אם מטריקה חרגה:
npm install -D @lhci/cli
// lighthouserc.js
module.exports = {
ci: {
collect: { url: ['https://staging.example.com/'], numberOfRuns: 3 },
assert: {
assertions: {
'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],
'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],
'total-blocking-time': ['error', { maxNumericValue: 200 }],
'resource-summary:script:size': ['error', { maxNumericValue: 170000 }],
},
},
},
};
numberOfRuns: 3 חשוב — Lighthouse רועש, והחציון מ-3 ריצות יציב יותר מריצה בודדת. שים לב שאני אוכף גם גודל JavaScript (170KB), לא רק מטריקות תוצאה — כי גודל הבנדל הוא הסיבה הכי שכיחה לנסיגת INP.
bundlesize / size-limit על הבנדל
נעילה ישירה על משקל ה-JS עוד לפני שמריצים דפדפן:
npm install -D size-limit @size-limit/preset-app
{
"size-limit": [
{ "path": "dist/assets/index-*.js", "limit": "150 KB" }
]
}
זה נכשל ב-CI ברגע שמישהו מושך ספרייה כבדה, לפני שזה בכלל מגיע ל-Lighthouse.
דוגמה מהשטח — לקחת LCP מ-4.1ש׳ ל-1.8ש׳
כדי שזה לא יישאר מופשט, הנה רצף תיקון אמיתי על אתר תדמית עם תמונת Hero כבדה:
- מצאתי את ה-LCP element עם ה-
PerformanceObserverמלמעלה — תמונת hero בגודל 2400px, 680KB JPEG. - המרתי ל-AVIF ב-
sharpב-quality 50 → 71KB. LCP ירד ל-3.0ש׳. - הוספתי
fetchpriority="high"+ preload → הדפדפן הפסיק לגלות אותה באיחור. LCP ל-2.4ש׳. - שלחתי srcset (400/800/1600) — המובייל הפסיק להוריד 1600px. על iPhone אמיתי ב-CrUX זה הוריד עוד 0.4ש׳.
- הזזתי את גופן ה-Hero ל-
font-display: optional+ preload → ה-FOUT נעלם והטקסט שמעל-הקיפול הופיע מיד. LCP יציב ב-1.8ש׳.
ארבעה שינויים, כולם מהמדריך הזה, אפס ספריות חדשות. זו רוב עבודת ה-LCP בעולם האמיתי.
הצ׳קליסט הסופי
לפני שאתה שולח לפרודקשן, עבור על כל הסעיפים:
TTFB
- [ ] CDN גלובלי מול הדומיין (Vercel / Cloudflare / Netlify)
- [ ] HTML סטטי או Edge function — לא Node serverless עם cold start
- [ ]
Cache-Controlנכון ב-headers - [ ] DB calls מ-cached / משולבים ב-Suspense
LCP
- [ ] מצאתי את ה-LCP element באמת
- [ ]
<link rel="preload" as="image" fetchpriority="high">עליו - [ ] AVIF + WebP fallback עם
<picture> - [ ]
widthו-heightמוגדרים - [ ]
loading="eager"(ולא lazy!) על ה-Hero - [ ] Critical CSS inline ב-head, מתחת ל-14KB
- [ ] CSS לא קריטי טעון
onload
INP
- [ ] אין Long Tasks מעל 200ms בנתיב האינטראקציה הראשית
- [ ]
scheduler.yield()(או polyfill) על משימות כבדות - [ ]
useDeferredValueאו debounce על קלט חיפוש - [ ] Virtualization לרשימות מעל 100 פריטים
- [ ] Third-party scripts ב-Partytown או async
CLS
- [ ] כל
<img>עםwidthו-height - [ ]
aspect-ratioב-CSS לכל קונטיינר תמונה - [ ]
min-heightל-ads, embed, iframe - [ ] פונטים עם
font-display: swapו-size-adjust - [ ] באנרים דינמיים — שמורים מקום או fixed bottom
- [ ] אנימציות עם
transform/opacityבלבד
תמונות
- [ ] sharp / vite-imagetools / next/image בבילד
- [ ] AVIF + WebP fallback
- [ ] srcset עם 3-4 גדלים, sizes נכון
- [ ]
loading="lazy"לכל מה שלא מעל הקיפול - [ ]
decoding="async"על תמונות לא קריטיות
פונטים
- [ ] Self-host (לא
fonts.googleapis.com) - [ ] WOFF2 בלבד
- [ ] Subset לעברית בלבד אם זה הצרכן העיקרי
- [ ]
font-display: swap(אוoptionalל-CLS מושלם) - [ ] Preload רק ל-LCP fonts
- [ ]
crossoriginב-preload
JS
- [ ] אנליטיקס אחרי
load/requestIdleCallback - [ ] Intercom / צ׳אט אחרי 5 שניות או scroll
- [ ]
asyncאוdeferעל כל סקריפט חיצוני - [ ] Partytown לסקריפטים כבדים (GTM, FB Pixel, Hotjar)
- [ ] בנדל מפוצל (code splitting) — לא monolith
CSS
- [ ] Critical CSS inline מתחת ל-14KB
- [ ]
content-visibility: autoלסקציות מתחת לקיפול - [ ] PurgeCSS / Tailwind content paths נכונים
- [ ] CSS Final מתחת ל-50KB
מדידה
- [ ]
web-vitalsv4 ב-RUM - [ ] שולח את המדידות לאיפשהו שאתה רואה
- [ ] עוקב ב-Search Console → Core Web Vitals שבועי
- [ ] CrUX dashboard לדומיין
עברת על הכל? תרוץ Lighthouse פעם אחת אחרונה. אם קיבלת 95+ בשלוש המטריקות — שלח לפרודקשן. ציון 85-95 אומר שיש עוד מה לסחוט; פחות מ-85 אומר שדילגת על משהו, אז תחזור על הצ׳קליסט.
שאלות נפוצות
למה ציון ה-Lighthouse שלי 98 אבל Search Console אומר שאני נכשל ב-Core Web Vitals?
כי הם מודדים שני דברים שונים. Lighthouse הוא Lab — מכונה אחת בתנאי בדיקה קבועים, ואינו מודד INP בכלל (רק TBT כמתאם). Search Console משתמש ב-Field data מ-CrUX — משתמשים אמיתיים על מכשירים אמיתיים על פני 28 ימים. אם רבע מהמשתמשים שלך עם מובייל בינוני שמקבל אינטראקציות איטיות, CrUX ייכשל גם כש-Lighthouse ירוק. תמיד תאמין ל-Field על פני Lab.
כמה זמן לוקח לראות שינוי ב-Search Console אחרי שתיקנתי?
CrUX הוא חלון נע של 28 ימים, אז גם אם תיקנת היום, הציון ימשיך לכלול נתונים ישנים עוד כמה שבועות. בפועל תתחיל לראות תנועה תוך 7-14 ימים ושיפור מלא תוך כ-28 ימים. כדי לראות מיד אם התיקון עבד, מדוד RUM משלך עם web-vitals — זה מעדכן בזמן אמת ולא ממתין ל-Google.
INP גרוע אבל אני לא מצליח לשחזר אותו אצלי — מה לעשות?
זה הסיפור הקלאסי של INP. אתה במחשב חזק, המשתמש שנכשל הוא על מובייל ישן. הפעל ב-DevTools → Performance את ה-CPU throttling פי 4 או פי 6 ותקבל קירוב טוב. אבל הדרך הוודאית היחידה היא RUM — שלח את ה-attribution של onINP (זמין ב-web-vitals/attribution) שאומר לך בדיוק איזה אלמנט ואיזה event handler גרמו לתקיעה. בלי זה אתה מנחש.
האם Core Web Vitals באמת משפיעים על דירוג Google?
כן, אבל כ-tiebreaker, לא כגורם דומיננטי. תוכן רלוונטי מנצח מהירות כמעט תמיד. ההשפעה האמיתית מורגשת כשיש שתי תוצאות דומות באיכות התוכן — אז המהיר יותר מקבל יתרון. בנוסף יש את האפקט העקיף: אתר מהיר = פחות נטישה = יותר זמן בדף = אותות חיוביים. אם אתה מטפל גם ב-GEO ואופטימיזציה למנועי תשובות, מהירות הטעינה משפיעה גם על כמה מהר הבוטים של ה-AI סורקים אותך.
מה ההבדל המעשי בין AVIF ל-WebP, ובמה לבחור ב-2026?
AVIF דחוס ב-20-25% יותר מ-WebP באותה איכות ונתמך כיום בכ-94% מהדפדפנים — Chrome, Firefox, Edge, Safari 16.4+. החיסרון היחיד הוא זמן הקידוד (פי 5-10 מ-JPEG), אבל זה קורה פעם אחת ב-build ולא מזיז למשתמש. ב-2026 ההמלצה היא לקודד את שניהם ולשרת דרך <picture>: AVIF ראשון, WebP fallback, JPEG לבסוף.
האם כדאי להוסיף loading="lazy" לכל התמונות?
לא לכולן — זו טעות נפוצה שהורסת LCP. תמונה שמופיעה מעל הקיפול (במיוחד ה-Hero, שלרוב היא ה-LCP element) צריכה loading="eager" ו-fetchpriority="high". רק תמונות שמתחת לקיפול מקבלות loading="lazy". lazy על אלמנט ה-LCP גורם לדפדפן לדחות אותו ולקבל LCP גרוע מאוד.
צריך RUM אם יש לי כבר Search Console ו-Lighthouse?
מומלץ מאוד. Search Console נותן לך נתון מצרפי באיחור של שבועות ובלי לומר לך איזה אלמנט אשם. RUM (עם web-vitals + attribution) נותן זמן אמת, פילוח לפי דף ומכשיר, וזיהוי מדויק של ה-element הבעייתי. בלעדיו אתה רואה ש-INP גרוע אבל לא יודע איפה. למדידה קלילה בחר כלי אנליטיקס שלא מכביד — ראה GA4 מול Plausible מול PostHog.
לסיכום
Core Web Vitals ב-2026 הם לא קסם — הם צ׳קליסט. הסדר הנכון הוא תמיד מלמטה למעלה: קודם TTFB (CDN + סטטי/Edge), כי הוא תקרת הזכוכית של כל השאר; אחר כך LCP (preload, AVIF, srcset, fetchpriority); INP הוא הקשה ביותר כי Lab לא תופס אותו, ולכן שבור Long Tasks עם scheduler.yield(), וירטואל רשימות, ודחה third-party; ו-CLS הוא הקל ביותר, עם מימדים על תמונות, aspect-ratio, ו-font-display נכון.
שני העקרונות שמחזיקים את הכל: מדוד Field (CrUX/RUM) ולא רק Lab, ונעל את ההישגים ב-תקציב ביצועים ב-CI כדי שלא ייפול שוב. אם תיישם את הצ׳קליסט הסופי שלמעלה סעיף-סעיף, רוב האתרים יגיעו ל-Good בשלוש המטריקות. ואם אתה בונה מחדש מאפס, התחל מתשתית שכבר עוברת — Next.js 16 ו-App Router נותנים לך SSR, Image Optimization וטעינת פונטים אופטימלית כברירת מחדל.
למי לפנות אם נתקעת
ביצועים זה תחום בלי קסם — או שעברת על הצ׳קליסט ויש לך מספרים, או שלא עברת. אם יש לך מצב ספציפי שלא הצלחת לפצח, שלח לי הודעה ב-WhatsApp ב-wa.me/972585802298 עם:
- URL של הדף הבעייתי
- צילום מסך מ-PageSpeed Insights
- המטריקה שהכי מטרידה אותך
לרוב אפשר לזהות את הצוואר תוך 10 דקות של DevTools.
מקורות
- web.dev — Core Web Vitals overview
- web.dev — Defining Core Web Vitals thresholds
- web.dev — INP becomes a Core Web Vital on March 12
- web.dev — Optimize long tasks
- web.dev — Best practices for measuring Web Vitals in the field
- Chrome for Developers — Use scheduler.yield() to break up long tasks
- Google Search Central — Understanding Core Web Vitals and Google search results
- Vercel — Fluid Compute
- Vercel — Production checklist
- Vercel — Image Optimization
- Next.js — Font Optimization
- Partytown — Run Third-Party Scripts From a Web Worker
- GitHub — web-vitals JavaScript library
- DebugBear — Measure And Optimize Google Core Web Vitals
- Cloudflare Blog — INP: Get ready for the new Core Web Vital
- Can I use — AVIF image format support
- Can I use — Scheduler API: yield
- MDN — Scheduler: yield() method
- Google Chrome — Lighthouse CI documentation