# Startup performance: paint and navigation timings

> Measure the wait before your app's content appears, using the paint entries first-paint and first-contentful-paint plus the navigation timing milestones.

**In one line:** Per MDN, the data `PerformancePaintTiming` provides "helps you
minimize the time that users have to wait before they can see the site's content start
to appear"; `PerformanceNavigationTiming` separately "provides methods and properties
to store and retrieve metrics regarding the browser's document navigation events."
Together those two entry types are what this page measures with.

## The paint moments the browser reports

Per MDN, `PerformancePaintTiming` "provides timing information about 'paint' (also
called 'render') operations during web page construction," where "'Paint' refers to
conversion of the render tree to on-screen pixels." MDN names two key paint moments
this API provides:

- **First Paint (FP)** — "Time when anything is rendered." MDN notes that "the
  marking of the first paint is optional, not all user agents report it."
- **First Contentful Paint (FCP)** — "Time when the first contentful paint — the
  first bit of DOM text or image content is rendered."

Per MDN's glossary, the FCP timestamp "indicates when the browser first rendered any
text, image (including background images), video, canvas that had been drawn into, or
non-empty SVG," and it is "the first time users could start consuming page content."

A third moment sits outside this interface: per MDN, Largest Contentful Paint (LCP) is
provided by the `LargestContentfulPaint` API and is the "render time of the largest
image or text block visible within the viewport."

## How to use it

Per MDN, `PerformanceObserver` "is used to observe performance measurement events and
be notified of new performance entries as they are recorded in the browser's
performance timeline." MDN's own example observes the `paint` entry type, using "the
`buffered` option to access entries from before the observer creation":

```js
const observer = new PerformanceObserver((list) => {
  list.getEntries().forEach((entry) => {
    // entry.name is either "first-paint" or "first-contentful-paint".
    console.log(`The time to ${entry.name} was ${entry.startTime} milliseconds.`);
  });
});

observer.observe({ type: 'paint', buffered: true });
```

Per MDN, a `paint` entry's `name` "returns either `first-paint` or
`first-contentful-paint`", its `startTime` is "the timestamp when the paint occurred",
and its `duration` "returns 0" — so the timestamp, not the duration, is the number you
want.

## Detecting and falling back

Per MDN, `Performance.getEntriesByType()` "only shows `paint` performance entries
present in the browser's performance timeline at the time you call this method." That
makes it the natural fallback when `PerformanceObserver` is unavailable:

```js
function readPaintTimings() {
  if ('PerformanceObserver' in window) {
    const observer = new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) report(entry.name, entry.startTime);
    });
    observer.observe({ type: 'paint', buffered: true });
    return;
  }
  // Fallback: no PerformanceObserver — read whatever is already on the timeline.
  if (!('getEntriesByType' in performance)) return; // Nothing to read; skip reporting.
  for (const entry of performance.getEntriesByType('paint')) {
    report(entry.name, entry.startTime);
  }
}
```

For the document milestones, `PerformanceNavigationTiming` is a single entry: per MDN,
"only the current document is included in the performance timeline, so there is only
one `PerformanceNavigationTiming` object in the performance timeline."

```js
const [navigation] = performance.getEntriesByType('navigation');
if (navigation) {
  // domInteractive: immediately before readyState is set to "interactive".
  // loadEventEnd: immediately after the load event handler completes.
  console.log(navigation.domInteractive, navigation.loadEventEnd);
} else {
  console.log('No navigation entry on this timeline.');
}
```

## Where it is supported

Per MDN, both interfaces are Baseline **widely available**: MDN describes each as a
feature that "is well established and works across many devices and browser
versions," and dates `PerformancePaintTiming` as "available across browsers since
April 2021" and `PerformanceNavigationTiming` as "available across browsers since
October 2021." MDN's compatibility data puts paint timing in Chrome 60, Edge 79,
Firefox 84, Safari 14.1 and Safari on iOS 14.5, and navigation timing in Chrome 57,
Edge 12, Firefox 58, Safari 15 and Safari on iOS 15.1 — Safari shipped last in both
cases, which is what those two Baseline dates track. Neither interface is marked
deprecated or experimental by MDN.

MDN attaches the same caveat to both Baseline statements — "some parts of this
feature may have varying levels of support" — and those exceptions are per-property,
not per-interface. Within the paint interface, MDN states the split explicitly:
`paintTime` "is broadly interoperable, whereas the `presentationTime` is
implementation-dependent." Several `PerformanceNavigationTiming` properties are marked
experimental by MDN, including `activationStart`, `confidence`, `criticalCHRestart`
and `notRestoredReasons`. So treat the interfaces as present and feature-detect at the
property level, not the interface level.

## Practical checklist

- [ ] **Don't require a first-paint entry.** Per MDN, marking the first paint is
      optional and not all user agents report it — code that waits for
      `first-paint` before reporting may wait forever. Key your reporting on
      `first-contentful-paint`.
- [ ] **Register late and you lose the entries.** `getEntriesByType()` returns only
      what is on the timeline when you call it, so an observer created after paint
      sees nothing unless you pass `buffered: true`.
- [ ] **Degrade through the paint timestamps, don't assume the newest one.** MDN's
      own example checks `presentationTime` first, falls back to `paintTime`, and
      falls back again to `loadTime` in non-supporting browsers.
- [ ] **FCP is not "the layout finished".** Per MDN it excludes iframe content but
      *includes* text with pending webfonts, so an FCP can be reported while the
      final typeface is still loading.
- [ ] **Adjust prerendered timings against `activationStart`.** Per MDN,
      `activationStart` represents "the
      time between when a document starts prerendering and when it is activated", so
      raw timestamps from a prerendered document are not comparable to a normal
      navigation's without accounting for it. MDN marks this property experimental.
- [ ] **Know what `duration` means here.** Per MDN, a navigation entry's `startTime`
      is `0` and its `duration` is the difference between `loadEventEnd` and
      `startTime` — a whole-document number, not a phase measurement.

## Where to go next

- [Core Web Vitals](/reference/performance/core-web-vitals/) — where LCP and the
  other field metrics are covered.
- [App shell architecture](/reference/performance/app-shell/) — a structural
  approach to what renders first.
- [Back/forward cache (bfcache)](/reference/performance/bfcache/) — a restore is not
  a fresh startup, and `notRestoredReasons` reports why one did not happen.