<- blog

Soft Navigation Metrics Break the Before-and-After Chart

Native soft-navigation measurement can raise pageviews and split Core Web Vitals by route. Preserve dual baselines before calling either a trend.

#analytics#web-performance#web-platform#operations

A website can report more pageviews tomorrow even when exactly the same people follow exactly the same routes.

Cloudflare warned about that possibility on 21 August while rolling out improved soft-navigation measurement in Web Analytics. Client-side route changes can now be classified as soft-navigation when Chrome's native API is available, or routing-apis when Cloudflare falls back to observing the Navigation or History APIs. Cloudflare says the change may alter pageview volume in both the dashboard and GraphQL API. Native support also exposes Largest Contentful Paint for route transitions that previously sat inside one long document lifecycle.

Three days earlier, a Chromium implementation change replaced time-based metric slicing with per-navigation data buckets keyed by navigation_id. It added explicit commit and completion callbacks and routes INP, CLS, and soft-LCP candidates to the navigation that owns them. That commit is implementation work rather than an analytics product announcement, but it exposes the underlying shift: the browser is learning to treat one document as several measurable experiences.

That is useful progress. It is also a measurement migration. A route that used to be invisible can become a pageview; a performance sample that used to describe the full document can become several route-level samples; and browser cohorts can produce different fields for the same journey.

The repeated angle to avoid

The ten most recent posts here covered API-denial control loops, credential isolation, cross-border payment routing, MCP detection, encrypted handshakes, deployment authentication, checkout fields, plugin trust, agent context copies, and form-security states. Older overlapping posts covered server-side conversion outcomes, bot-contaminated analytics, command-line analytics checks, cache variants, and performance-oriented audit pipelines.

The weak version of this article is the old X needs Y formula: SPAs need better analytics, so enable soft-navigation tracking. That misses the harder operator problem. Better detection changes the definition and denominator of metrics that businesses already compare over time.

The thesis is narrower: browser-native route boundaries turn a document-level dataset into a mixed route-level dataset, so the rollout must be managed as a schema migration rather than accepted as an ordinary trend line. The practical task is to separate real customer behaviour from three simultaneous changes: newly counted route views, newly sliced performance lifecycles, and uneven browser support.

Fresh evidence and background context

The source map separates this week's implementation evidence from the protocol and product documentation needed to interpret it:

Source Freshness What it contributes
Cloudflare: improved soft-navigation measurement 21 August 2026 The pageview-volume warning, new navigationType values, native Chrome path, routing-API fallback, and LCP coverage difference
Chromium: route metrics by navigation_id 18 August 2026 Fresh implementation evidence for per-navigation INP, CLS, and LCP buckets; explicit commit/completion lifecycles; bounded pending-navigation state
Chrome: soft-navigation measurement Background, updated 21 July 2026 Chrome 151 launch posture, native detection criteria, metric attribution mechanics, cross-browser limits, and dual-measurement recommendation
Cloudflare Web Analytics navigation dimensions Current primary documentation The queryable distinction between hard loads, native soft navigations, and routing-API fallbacks
Cloudflare SPA measurement docs Current primary documentation Automatic Navigation API and History API observation, plus the ability to disable SPA tracking

The new information surplus sits between these sources. Cloudflare tells operators that counts may move. Chrome explains why route-level performance is not equivalent to cold-page performance. Chromium's implementation shows why attribution needs a navigation identity and lifecycle rather than timestamps alone. Joined together, they imply a migration method: preserve the old document baseline, label every new route sample by measurement path, and reconcile commercial outcomes separately.

One document can now contain several measured pages

Traditional analytics gets a convenient boundary for free. The browser loads a document, the analytics script starts, performance entries accumulate, and unloading or hiding the page finalises much of the record.

A client-routed application keeps the document alive:

hard load: /products
  -> click product
soft route: /products/blue-chair
  -> choose delivery
soft route: /checkout/delivery
  -> confirm
soft route: /checkout/review

Before route-aware measurement, that journey can look like one pageview with a long document lifetime unless the application or analytics SDK emits its own virtual views. After route-aware measurement, it can look like four pageviews and several performance intervals.

No extra person arrived. No acquisition channel improved. No new session began. The observation model found three transitions it previously missed.

Chrome's native definition is intentionally behavioural. A soft navigation requires a user action, a visible URL change, and a visible paint. The resulting entry carries a new URL, an initiating interaction, and a navigationId. That identifier matters because the same URL can be visited repeatedly during one document lifetime and because paint or interaction entries can cross the instant at which the route commits.

The 18 August Chromium change makes that lifecycle concrete. Metrics are routed into per-navigation buckets; a new commit completes the previously active navigation; early entries for a future navigation can wait in bounded pending state; stale uncommitted buckets are pruned. The exact internal classes are not an analytics contract, but the mechanism explains why a timestamp-only join is fragile. Paint can occur before the URL update, an interaction can straddle the boundary, and a long-lived app can revisit the same path.

An analytics event therefore needs an occurrence identity, not merely path + time.

Three navigation classes do not carry equal evidence

Cloudflare's new navigationType distinction is more than a reporting convenience. It separates three different measurement contracts.

Navigation class Boundary evidence Pageview Soft-route LCP Main limitation
Hard navigate and related load types Browser document navigation Yes Not applicable; ordinary page-load LCP Describes a fresh or restored document lifecycle, not an in-document route
soft-navigation Native API detects user action + URL change + visible paint Yes Available Currently a Chromium-support cohort rather than universal browser evidence
routing-apis Navigation API or History API activity observed by the analytics script Yes Unavailable in Cloudflare's fallback API activity is a fallback heuristic and does not provide the native paint boundary

This produces a subtle mixed dataset.

A Chrome visitor can generate a soft-navigation record with route-level LCP. A Safari or Firefox visitor following the same path can generate routing-apis, which supplies a pageview and other available metrics but not route-level LCP. An older browser may follow another fallback path. A hard refresh of the destination creates navigate and measures a cold or restored document load instead.

The route /checkout/review can consequently have:

Chrome client route  -> pageview + soft-route LCP
Safari client route  -> pageview + no soft-route LCP
hard deep link        -> pageview + document-load LCP

Those are not interchangeable performance samples. If a dashboard groups all three under the route and displays one LCP percentile without exposing navigation type and browser support, its population is conditional in ways the URL alone cannot reveal.

The immediate rule is to preserve navigationType as a first-class dimension. Do not flatten it during export, aggregation, or warehouse ingestion.

Route-level LCP answers a different question

A soft-navigation LCP is not simply the destination page's normal LCP measured later.

On a hard load, every newly painted element can compete for LCP. On a soft route, shared shell content may already exist. Chrome's documentation gives the useful example of a large banner that is LCP on initial load while only the changing text below it becomes eligible during a later soft navigation. Deep-link directly to the same route and the banner is new again, so it can become LCP.

The two samples answer different questions:

  • Hard-load LCP: How quickly did the destination become visually useful when loaded as a document?
  • Soft-route LCP: How quickly did newly painted route content become visually useful after the interaction that changed the route?

Both matter. A buyer can land directly on a product page from search and later move client-side into checkout. Improving only the second can leave acquisition pages slow. Improving only the first can leave an app shell that feels sluggish after every click.

The arithmetic also changes at boundaries. Chrome says soft-navigation timings remain relative to the original hard-navigation clock, so route metrics must subtract the soft-navigation start time. Paints can occur before the URL update; for LCP mapping, the initiating interactionId can be more reliable than the paint entry's current navigationId. When a new soft route starts, the previous route's LCP, CLS, and INP interval is finalised and the next interval is reset.

That reset changes distributions:

  • one long document-level CLS value can become several route-level CLS values;
  • one document-level INP population can become route-specific interaction populations;
  • one hard-navigation LCP sample can be joined by several soft-route LCP samples;
  • routes with no subsequent navigation may finalise differently from routes followed immediately by another transition;
  • unsupported browsers still contribute ordinary metrics without contributing the same soft-route LCP records.

A lower route-level LCP percentile after rollout may reflect easier-to-paint incremental content, a different browser mix, or more samples per session. It is not automatically a performance improvement.

Pageviews now require a lineage field

Pageview has always been a derived event. This rollout makes the derivation impossible to ignore.

Use at least four fields when the analytics system permits them:

{
  "metric_version": "soft-nav-rollout-2026-08",
  "path": "/checkout/review",
  "navigation_type": "navigate|soft-navigation|routing-apis",
  "browser_family": "chromium|safari|firefox|other",
  "journey_id": "privacy-safe-session-or-flow-id",
  "navigation_id": "provider-or-browser-navigation-id",
  "occurred_at": "2026-08-22T15:00:00Z",
  "pageview": 1,
  "lcp_ms": 820,
  "business_outcome": null
}

The example is a warehouse or event-contract pattern, not a claim that every Cloudflare field is exported with those exact names. The important additions are metric_version, navigation_type, and a navigation occurrence identity.

metric_version marks the definition change. Without it, analysts will compare July and September as if pageview meant the same thing. navigation_type preserves the evidence class. navigation_id prevents repeated visits to the same URL inside one document from collapsing into one record. journey_id supports funnel sequencing without pretending every route transition is a new visitor.

Keep business_outcome separate. A newly visible checkout route is still not an order. The order database or payment system remains the commercial source of truth.

Use two baselines during the transition

Chrome's documentation recommends measuring both the traditional whole-document method and the soft-navigation method during the transition so browser and historical comparisons remain possible. That should become an operator rollout, not just an SDK option.

Baseline A: document continuity

Keep the existing view of sessions, hard page loads, whole-document Core Web Vitals, and verified outcomes. This is the historical comparison line. Do not rewrite old reports to simulate route views unless the reconstruction is genuinely equivalent and clearly versioned.

Baseline B: route experience

Start a new series for route views and route-sliced performance. Segment it by:

  • navigationType;
  • browser family and version;
  • path or route template;
  • hard entry versus client-side transition;
  • device class;
  • release or analytics schema version.

Run both long enough to cover the site's normal weekly cycle, campaign mix, and browser distribution. Low-traffic businesses may need several trading cycles rather than seven calendar days.

Do not create one conversion rate by dividing stable order counts by newly expanded pageviews and call the resulting decline real. During migration, calculate both:

historical conversion = verified outcomes / historical eligible sessions
route-step conversion = verified outcomes / eligible route occurrences

The denominators answer different questions. The first describes business acquisition and completion. The second helps diagnose movement through an in-app path. Neither should silently replace the other.

A decision table for changed numbers

When a dashboard moves after rollout, classify the change before acting.

Observed change First diagnostic Likely measurement explanation Business proof required
Pageviews rise, sessions and outcomes stay flat Split by navigationType and route Previously invisible client routes are now counted None yet; verify route sequence and deduplication
Conversion rate falls using pageviews Recalculate with the old eligible denominator Denominator expanded while outcomes stayed stable Orders, qualified leads, or bookings actually declined
LCP sample count rises in Chrome Split native soft routes from hard loads One document now emits route-level samples Compare fixed route + navigation-type + browser cohorts
Safari route LCP appears absent Inspect routing-apis records Fallback counts route views but cannot provide native soft-route LCP Use available metrics and synthetic route tests; do not impute LCP
Route LCP is much faster than hard-load LCP Compare client transition with direct deep link Shared shell and cached content are excluded from new paints Test both entry modes because customers use both
One route suddenly dominates views Reproduce its History/Navigation API behaviour Duplicate listener, redirect, replace/push sequence, or newly detected transitions Confirm unique route occurrences against journeys
CLS or INP improves globally Hold sample definition and browser mix constant Lifecycle values were reset into smaller intervals Re-run fixed-cohort RUM and user-journey tests

This table prevents two opposite errors: dismissing a real regression as analytics noise and treating analytics reclassification as customer growth.

A soft-navigation migration receipt

Keep the measurement change beside release and analytics configuration, not only in a dashboard annotation.

soft_navigation_migration:
  owner: web_analytics
  enabled_at: 2026-08-22T15:00:00Z
  provider: cloudflare_web_analytics
  metric_version_before: document_views_v1
  metric_version_after: document_plus_route_views_v2
  affected_surfaces:
    - product_routes
    - checkout_routes
    - account_routes
  expected_navigation_types:
    - navigate
    - soft-navigation
    - routing-apis
  historical_baseline_frozen: true
  dual_reporting_until: 2026-09-19
  required_segments:
    - navigation_type
    - browser_family
    - route_template
    - device_type
  guardrails:
    verified_orders_source: commerce_database
    qualified_leads_source: crm
    do_not_rebase_campaign_targets_automatically: true
    do_not_compare_mixed_lcp_populations: true
  acceptance_tests:
    - hard_load_each_critical_route
    - client_route_each_critical_route
    - browser_back_and_forward
    - same_route_revisited_in_one_document
    - direct_deep_link_then_client_transition
    - chromium_native_soft_navigation
    - safari_or_firefox_routing_fallback
    - duplicate_pageview_check
  rollback:
    provider_spa_measurement_can_be_disabled: true
    preserve_raw_navigation_type_data: true

The receipt does not require rolling back merely because counts change. Its purpose is to record that a discontinuity was expected, which reports are affected, which outcomes remain authoritative, and how to test the implementation.

Test boundaries, not just routes

A route matrix should exercise the places where attribution becomes ambiguous.

  1. Hard entry. Load each commercially important path directly and record the navigation type and performance fields.
  2. Client transition. Reach the same path through the intended in-app link or form action.
  3. Back and forward. Confirm browser history produces the expected route sequence without duplicate views.
  4. Replace versus push. Check filters, tabs, modals, and redirects that alter history but may not represent a new page to the user.
  5. Delayed URL update. Exercise a route where content paints before the URL changes; verify the sample is assigned to the intended occurrence.
  6. Repeated path. Visit the same route twice during one document lifetime and confirm two occurrences remain distinguishable.
  7. Long-lived session. Navigate through enough routes to expose listener leaks, buffering assumptions, and duplicate instrumentation.
  8. Cross-browser cohort. Compare native Chromium records with Safari and Firefox fallback records without requiring identical fields.
  9. Outcome reconciliation. Confirm orders, leads, bookings, and account changes remain one verified business event despite several route views.

Use Chrome DevTools' Performance panel to inspect native soft-navigation and LCP markers on the Chromium path. Use provider exports or GraphQL results to inspect navigationType. Use the business system to verify the outcome. No single layer proves all three.

Failure modes worth reviewing

The growth report celebrates a detector rollout

Pageviews increase after the analytics provider begins observing more client routes. Sessions, customers, orders, and qualified leads do not change. Annotate the schema boundary and restate growth reports with a stable denominator.

A route-level LCP replaces the deep-link experience

The client transition is fast because the shell and assets are warm, so the team stops testing direct entries from search, ads, email, bookmarks, or shared links. Keep hard-load and soft-route LCP as separate experience classes.

Browser support becomes a hidden performance filter

Only native-support browsers contribute route LCP, but the dashboard presents the percentile as if it covers all route visitors. Report coverage by browser and navigation type beside the percentile.

History changes become duplicate views

Application code, a router, and an analytics integration all observe the same transition, or a redirect produces several history operations. Reproduce the sequence and deduplicate by a provider event ID or navigation occurrence—not merely URL and second.

A visual state is mistaken for a page

A filter, drawer, tab, sort control, or modal changes the URL and paints content. Native criteria may classify it as a soft navigation even when product analytics considers it an interaction within one page. Preserve the raw navigation event, then map it to the business route taxonomy deliberately rather than deleting inconvenient measurements.

Whole-document trends and route trends are blended

A warehouse table unions old document samples and new route samples without a metric version. Percentiles and conversion rates move even inside reports that never adopted the new route analysis intentionally. Make incompatible rows fail a query or require an explicit grouping field.

The analytics change masks a real regression

Because a measurement migration is underway, every changed chart is dismissed as expected noise. Fixed-cohort checks can still detect regressions: same path, same navigation type, same browser family, same device class, before and after the application release.

The practical conclusion

Soft-navigation measurement repairs a genuine blind spot. Modern sites can make a visitor wait for meaningful content several times while keeping one document alive, and document-only pageviews and Core Web Vitals cannot describe those experiences well.

The repair changes the dataset it improves. Cloudflare's rollout can increase pageview counts and creates separate native and fallback navigation classes. Chrome's API gives route transitions identities, paints, and performance intervals, while current browser support leaves route-level LCP coverage uneven. Chromium's implementation work shows why the boundary needs explicit commit, completion, and per-navigation state rather than a loose time window.

Treat the rollout like a schema migration. Freeze the historical baseline. Start a route-level baseline. Keep navigation type, browser cohort, and metric version. Compare hard entries with client transitions. Reconcile commercial outcomes outside the pageview stream. Test repeats, history movement, delayed paints, and long-lived sessions.

Then the new numbers can do the job they were designed for: reveal which in-app routes feel slow and which journeys contain more steps than the old document model could see—without turning an instrumentation improvement into imaginary traffic, a false conversion decline, or an unsupported claim that the whole site became faster overnight.

Need technical help?

I'm a software engineer who builds web apps, APIs, and AI tooling. If you've got a project or a problem to talk through, book a free 30-minute call.

Book time with me ->