אופטימיזציית Total Blocking Time (TBT) במפתח אחד
זמן חסימה כולל גבוה (TBT) הוא הגורם העיקרי ל-INP ירוד (Interaction to Next Paint) וכתוצאה מכך לאובדן המרות. אנו רואים זאת בכל ביקורת שנייה: אתרים עם React ו-Next.js מציגים לעיתים קרובות TBT מעל 500 אלפיות השנייה, מה שפוגע ישירות בחוויית המשתמש. הצוות שלנו, עם ניסיון של 5+ שנים באופטימיזציית Core Web Vitals, יודע כיצד להוריד את ה-TBT לאזור הירוק — מתחת ל-200 אלפיות השנייה בדסקטופ ו-300 אלפיות השנייה במובייל עם הגבלת CPU פי 4.
TBT מורכב ממשימות ארוכות (Long Tasks), שכל אחת מהן נמשכת יותר מ-50 אלפיות השנייה. אם שלוש משימות של 80 אלפיות השנייה מבוצעות, החסימה הכוללת מסתכמת ב-90 אלפיות השנייה. כל אלפית שנייה כזו מעכבת את רינדור הפריים הבא, ומגדילה את הזמן עד לאינטראקטיביות מלאה. הפחתת TBT ב-100 אלפיות השנייה יכולה לשפר את ה-INP ב-30–50%, כפי שאושר במדידות שטח.
אנו מציעים אופטימיזציית TBT מקיפה במפתח אחד: מאבחון ועד הטמעת ניטור. בקשו ביקורת — נכין תוכנית עם לוחות זמנים והערכת השפעה ללא עלות.
כיצד לאבחן את הגורמים ל-TBT גבוה?
השלב הראשון הוא איתור משימות ארוכות. ב-Chrome DevTools, בכרטיסיית Performance: הקליטו טעינת עמוד, חפשו פסים אדומים מעל ה-thread הראשי. פרוגרמטית, השתמשו ב-Long Task API:
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log({
name: entry.name,
duration: entry.duration,
startTime: entry.startTime,
attribution: entry.attribution,
});
}
});
observer.observe({ type: 'longtask', buffered: true });
// Данные можно отправлять в аналитику через fetch с keepaliveconst observer = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { console.log({ name: entry.name, duration: entry.duration, startTime: entry.startTime, attribution: entry.attribution, }); } }); observer.observe({ type: 'longtask', buffered: true }); // Данные можно отправлять в аналитику через fetch с keepalive מציג באיזה פריים או סקריפט התרחשה המשימה. זהו הצעד הראשון לקראת אופטימיזציה ממוקדת.
גורם #1: JavaScript כבד באתחול
הגורם השכיח ביותר ל-TBT גבוה הוא bundle JS גדול שמנותח ומבוצע באופן סינכרוני בעת הטעינה. כל מגה-בייט של JS דורש כשנייה אחת של ניתוח במכשיר מובייל ממוצע. פתרון: פיצול קוד (code splitting) + טעינה עצלה (lazy loading):
const HeavyChart = lazy(() => import('./HeavyChart'));
const DataTable = lazy(() => import('./DataTable'));
function Dashboard() {
const [showChart, setShowChart] = useState(false);
return (
<div>
<button onClick={() => setShowChart(true)}>Показать график</button>
{showChart && (
<Suspense fallback={<Skeleton />}>
<HeavyChart />
</Suspense>
)}
</div>
);
}לנתיבים: React.lazy + react-router. פיצול קוד מפחית את ה-TBT פי 2–3, מכיוון שהחלק הראשוני הופך לפחות מ-150 kB gzip.
גורם #2: סקריפטים סינכרוניים של צד שלישי
Google Tag Manager, צ'אטים, פיקסלים של רשתות פרסום — כל סקריפט של צד שלישי יכול ליצור משימות ארוכות. אבחון ב-Chrome DevTools: Performance → Bottom-up → קיבוץ לפי דומיין. פתרונות:
<!-- Используем async или defer: -->
<script src="https://widget.example.com/chat.js" async></script>
<!-- Или отложенная загрузка после взаимодействия: -->
<script>
function loadChat() {
const s = document.createElement('script');
s.src = 'https://widget.example.com/chat.js';
document.head.appendChild(s);
}
['click', 'scroll', 'keydown'].forEach(event => {
window.addEventListener(event, loadChat, { once: true });
});
</script>עבור GTM: טריגר מושהה "DOM Ready" במקום "Page View" עבור תגים לא קריטיים.
גורם #3: חישובים כבדים על ה-thread הראשי
מיון מערכים גדולים, מניפולציות DOM מורכבות — כל אלה חוסמים את ה-thread הראשי. Web Worker מעביר חישובים ל-thread נפרד:
// worker.js
self.onmessage = function(e) {
const { data, operation } = e.data;
let result;
switch (operation) {
case 'sort':
result = heavySort(data);
break;
case 'filter':
result = complexFilter(data);
break;
}
self.postMessage(result);
};
// main.js
const worker = new Worker('/worker.js');
worker.postMessage({ data: largeArray, operation: 'sort' });
worker.onmessage = (e) => setTableData(e.data);כדי לפרק לולאות סינכרוניות ארוכות, השתמשו ב-attribution (Chrome 94+) או ב-polyfill דרך const HeavyChart = lazy(() => import('./HeavyChart')); const DataTable = lazy(() => import('./DataTable')); function Dashboard() { const [showChart, setShowChart] = useState(false); return ( <div> <button onClick={() => setShowChart(true)}>Показать график</button> {showChart && ( <Suspense fallback={<Skeleton />}> <HeavyChart /> </Suspense> )} </div> ); } .
גורם #4: Hydration כבד ב-SSR/SSG
Next.js, Nuxt, Gatsby — במהלך hydration, ה-framework עובר שוב על כל ה-DOM. פתרונות: Hydration חלקי (ארכיטקטורת Islands), Hydration פרוגרסיבי, React 18 <!-- Используем async или defer: --> <script src="https://widget.example.com/chat.js" async></script> <!-- Или отложенная загрузка после взаимодействия: --> <script> function loadChat() { const s = document.createElement('script'); s.src = 'https://widget.example.com/chat.js'; document.head.appendChild(s); } ['click', 'scroll', 'keydown'].forEach(event => { window.addEventListener(event, loadChat, { once: true }); }); </script> :
import { startTransition } from 'react';
function SearchResults() {
const [results, setResults] = useState([]);
function handleSearch(newQuery) {
startTransition(() => setResults(fetchResults(newQuery)));
}
} מדידת TBT בשטח
לא ניתן למדוד TBT ישירות בדפדפן, אך INP הוא פרוקסי ייצור שלו. אנו משתמשים ב-// worker.js self.onmessage = function(e) { const { data, operation } = e.data; let result; switch (operation) { case 'sort': result = heavySort(data); break; case 'filter': result = complexFilter(data); break; } self.postMessage(result); }; // main.js const worker = new Worker('/worker.js'); worker.postMessage({ data: largeArray, operation: 'sort' }); worker.onmessage = (e) => setTableData(e.data); :
npm install web-vitals import { onINP } from 'web-vitals';
function sendToAnalytics(metric) {
navigator.sendBeacon('/api/vitals', JSON.stringify({
name: metric.name,
value: metric.value,
rating: metric.rating,
id: metric.id,
url: location.href
}));
}
onINP(sendToAnalytics);הגדירו התראות כאשר INP חורג מהאזור הירוק.
השוואת שיטות אופטימיזציית TBT
| שיטה | יעילות | זמן הטמעה | מורכבות |
|---|---|---|---|
| פיצול קוד | מפחית TBT פי 2-3 | 1–2 ימים | בינונית |
| Web Worker | מוריד עומס מה-thread הראשי עד 50% | יום אחד | נמוכה |
| טעינה מושהית של סקריפטים | מסיר 100–500 אלפיות השנייה | חצי יום | נמוכה |
| אופטימיזציית Hydration | מפחית TBT עד 40% | 3–5 ימים | גבוהה |
רשימת בדיקה לאופטימיזציית TBT
- פיצול bundle JS: חלק ראשוני < 150 kB gzip, נתיבים בטעינה עצלה
- סקריפטים של צד שלישי: כל הלא קריטיים עם
scheduler.yield()או טעינה מושהית - חישובים כבדים הועברו ל-Web Worker
- לולאות סינכרוניות ארוכות פוצלו דרך
setTimeout - React:
startTransitionלעדכונים לא קריטיים,import { startTransition } from 'react'; function SearchResults() { const [results, setResults] = useState([]); function handleSearch(newQuery) { startTransition(() => setResults(fetchResults(newQuery))); } }לסינון - נבדקו רשתות פרסום: AdSense, Yandex Ads מוסיפים לעיתים קרובות 200–500 אלפיות השנייה ל-TBT
מה כלול בעבודה
- אבחון TBT ומשימות ארוכות עם דוח על מקורות החסימה
- תוכנית אופטימיזציה עם צעדים ספציפיים והערכות השפעה
- הטמעת פיצול קוד, טעינה עצלה, Web Workers
- אופטימיזציה של סקריפטים של צד שלישי ו-hydration
- הגדרת ניטור INP בשטח עם התראות
- מסירת תיעוד וגישה לכלים
- ייעוץ ושבועיים של תמיכה לאחר המסירה
לוחות זמנים ותמחור
אבחון ותכנון: 1–2 ימי עסקים. פיצול קוד + טעינה עצלה עבור אפליקציית React טיפוסית: 3–5 ימי עסקים. מחזור מלא: אבחון, אופטימיזציה, הגדרת ניטור INP בייצור — 1.5–3 שבועות בהתאם למורכבות האפליקציה. התמחור מחושב באופן אישי לאחר הביקורת.
צרו קשר — נעריך את הפרויקט שלכם ונכין תוכנית אופטימיזציית TBT עם תוצאות מובטחות.







