Olá. Em artigos anteriores, falamos sobre os aspectos básicos da otimização: um e dois . Hoje, proponho mergulhar em uma parte das tarefas que a equipe de arquitetura de front-end da hh.ru está realizando.
Trabalho na equipe de arquitetura. Não apenas transferimos arquivos de uma pasta para outra, mas também fazemos um monte de outras coisas:
- Desempenho do aplicativo
- Infraestrutura: montagem, testes, pipelines, implementação em produção, ferramentas de desenvolvedor (por exemplo, plug-ins Babel, regras eslint personalizadas)
- Sistema de design (UIKit)
- Mudando para novas tecnologias
Se você vasculhar, poderá encontrar muitas coisas interessantes.
Portanto, vamos falar sobre desempenho. A equipe de arquitetura de front-end é responsável pelo front-end e o back-end (SSR).
Eu sugiro olhar para as métricas e entender como respondemos a diferentes gatilhos. O artigo será dividido em 2 partes. Servidor e cliente. Gráficos, código e história legal estão anexados.

Neste artigo, vamos falar sobre como rastreamos, quais resultados (interessantes) existem. Para a teoria e quanto é melhor consultar o primeiro artigo.
Cliente
— . , , , . - , .
. . , , , . . :
FMP (first meaningful paint)

FMP : . — . TOP . 95 . .
, :

FMP :
- hh.ru — . , , , — , .
- — . — .
FMP — . FMP? .
, FMP - :
requestAnimationFrame(function() {
// renderTree
var renderTreeFormed = performance.now();
requestAnimationFrame(function() {
//
var fmp = performance.now();
//
window.globalVars.performance.fmp.push({
renderTreeFormed: renderTreeFormed,
fmp: fmp
})
});
});
:
- body, ( , 1 ). , .
- — ¯(ツ)/¯
, raf setTimeout\interval . .
, - . PageVisibility API:
window.globalVars = window.globalVars || {};
window.globalVars.performance = window.globalVars.performance || {};
// ,
window.globalVars.performance.pageWasActive = document.visibilityState === "visible";
document.addEventListener("visibilitychange", function(e) {
// - —
if (document.visibilityState !== "visible") {
window.globalVars.performance.pageWasActive = false;
}
});
FMP:
requestAnimationFrame(function() {
// renderTree
var renderTreeFormed = performance.now();
requestAnimationFrame(function() {
//
var fmp = performance.now();
// ,
// ,
if (window.globalVars.performance.pageWasActive) {
window.globalVars.performance.fmp.push({
renderTreeFormed: renderTreeFormed,
fmp: fmp
});
}
});
});
, . .
: — , " " , . .
, , ? , , renderTree () , , , .
. ( fmp_menu ):
<script>window.performance.mark('fmp_body')</script>
:

: , .
:
- FMP . , 3 . "" .
- FMP: 10 . .
- , FMP . , .
- , ! , url-. , , 95 :

, . , , .
TTI
TTI . , Page Visibility API, . , TTI longtask " - -", , .
function timeToInteractive() {
// TTI
const LONG_TASK_TIME = 2000;
// TTI,
const MAX_LONG_TASK_TIME = 30000;
const metrics = {
report: 'TTI_WITH_VISIBILITY_API',
mobile: Supports.mobile(),
};
if ('PerformanceObserver' in window && 'PerformanceLongTaskTiming' in window) {
let timeoutIdCheckTTI;
const longTask = [];
const observer = new window.PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
longTask.push(Math.round(entry.startTime + entry.duration));
}
});
observer.observe({ entryTypes: ['longtask'] });
const checkTTI = () => {
if (longTask.length === 0 && performance.now() > MAX_LONG_TASK_TIME) {
clearTimeout(timeoutIdCheckTTI);
}
const eventTime = longTask[longTask.length - 1];
if (eventTime && performance.now() - eventTime >= LONG_TASK_TIME) {
if (window.globalVars?.performance?.pageWasActive) {
StatsSender.sendMetrics({ ...metrics, tti: eventTime });
}
} else {
timeoutIdCheckTTI = setTimeout(checkTTI, LONG_TASK_TIME);
}
};
checkTTI();
}
}
export default timeToInteractive; TTI (95", TOP ):

: TTI ? :
- , requestIdleCallback
- 3d party
, " ". :
()
95" TOP :

? , JS , .
, js , , , .
JS
, hydrate, , :

, :
- , , ( \ , - )
- — . , :

?
- FMP, , . , , SSR .

- , . . .
LongTasks
PerformanceObserver :

:
, , (, 2020!). : ! . .
: , js reflow, event loop 30 .
, . , . , ;)
2 :
- ,
- Longtasks. — .
?
, . -, , rate + , , FID. .
, .
. — . , .
? , .
, , CPU, — . SSR. :

http

— , TOP . 95 . , 12:10 12:40. " ", 400 , , " ". :
c

, parse time.
CPU. :

, . 1 . .
: . 12 , . 150, 400 .
. . , , slack :

.
render parse time :

, :

,
TypeError: Cannot read property 'map' of undefined
at Social (at path/to/module)
, .
, , , :

, parse time :

. . :

SSR as a service. BFF, node.js , . BFF .
, , , : BFF , node.js. — BFF . , .
BFF . \ .
:
. .
, .
, . . FMP. , , , , . - . : , , , \ , .
.