Render Profiler
Render Profiler records lightweight React render summaries and uploads them together with the page TTI log. It is useful when a page TTI movement needs a quick answer to "which React subtree spent the most render time?".
Enable Upload
Render Profiler upload needs both page TTI and the render-profiler feature control.
- The page must have a valid
ttiConfigentry for the current Next pathname. Render Profiler summaries are attached to the page TTI log, so a page without TTI will not upload the profiler summary.
// packages/<domain>/app-desktop/tti.ts
const ttiConfig = {
widgets: ['accom.hotelDetail'],
strategies: {
'/hotel/detail': {
name: 'renderAny',
widgets: ['accom.hotelDetail'],
},
},
};
export default ttiConfig;
- The
render-profilerfeature control must be enabled for the route.
{
"render-profiler": {
"enabled": true,
"properties": {
"on": ["/hotel/detail"],
"partial": ["/hotel/[country]/[hotel]"]
}
}
}
- A pathname listed in
properties.onis profiled for 100% of traffic. - A pathname listed in
properties.partialis profiled for 50% of traffic. - The 50% decision is kept stable until the current page lifecycle ends.
properties.ontakes priority overproperties.partial.
Default Recording
Every page is already wrapped by a root profiler boundary with the profile id RENDER_PROFILE_ROOT. When render-profiler is enabled for the current pathname, it records committed renders for the page component subtree.
Product teams can add more RenderProfiler boundaries around specific components. These boundaries do not change the rendered DOM structure. They only add named React Profiler measurements when the provider is enabled.
TTI Log Shape
Render Profiler summaries are uploaded only on the page TTI log under @props.profile. Widget TTI logs do not include this field.
When profiling is enabled and at least one profiler event is collected, the log looks like this:
{
"profile": {
"enable": true,
"RENDER_PROFILE_ROOT": {
"mount": 1,
"update": 2,
"nested": 0,
"duration": 30,
"p95": 20
},
"HDP_DESKTOP_HEADER": {
"mount": 1,
"update": 0,
"nested": 0,
"duration": 12,
"p95": 12
}
}
}
When no profiler summary is collected, the page TTI log still includes:
{
"profile": {
"enable": false
}
}
Only the top 10 profileIds by total render duration are uploaded. Keep the number of custom profiler boundaries small and focused. Prefer wrapping suspected expensive regions instead of every component, list item, or dynamically generated id.
Uploaded Values
Each uploaded profile id contains:
| Field | Meaning |
|---|---|
mount | Number of committed React Profiler mount phase renders. |
update | Number of committed React Profiler update phase renders. |
nested | Number of committed React Profiler nested-update phase renders. |
duration | Sum of actualDuration for the uploaded profile id, in milliseconds. |
p95 | Nearest-rank p95 of per-commit actualDuration, in milliseconds. |
The browser snapshot API returns a richer local view:
| Field | Meaning |
|---|---|
profileId | The profiler boundary id. |
mountCount | Number of committed mount phase renders. |
updateCount | Number of committed update phase renders. |
nestedUpdateCount | Number of committed nested-update phase renders. |
totalDuration | Sum of actualDuration, in milliseconds. |
p95Duration | Nearest-rank p95 of per-commit actualDuration, in milliseconds. |
Stop Recording
Recording stops after page TTI is uploaded. Existing profiler events stay readable, but later React Profiler callbacks are ignored for that page lifecycle.
For client-side navigation, a new profiling session starts on routeChangeStart. This clears the previous event buffer so the next page records from a clean session.
Console Snapshot
When Render Profiler is enabled, the browser console exposes window.__RENDER_PROFILE__.
Print the full snapshot:
window.__RENDER_PROFILE__.snapshot();
Print a table of profiler summaries:
console.table(window.__RENDER_PROFILE__.snapshot().summaries);
Reset the current profiling session manually:
window.__RENDER_PROFILE__.reset();
If window.__RENDER_PROFILE__ is undefined, the current page is not in an enabled Render Profiler session.
Add Component Profiling
Import RenderProfiler from @traveloka/core and wrap a specific subtree with a stable profileId.
import { RenderProfiler } from '@traveloka/core';
<RenderProfiler profileId="HDP_DESKTOP_HEADER">
<DesktopHeaderContainerV2
enableGoogleOneTap={!isEnabled}
isStatic
openLinksInNewTab
/>
</RenderProfiler>;
Use a stable, low-cardinality profileId. Good ids describe the page and component region, such as HDP_DESKTOP_HEADER. Avoid ids that include hotel id, search id, index, date, user id, or any value that can create many unique keys in Datadog.