diff --git a/docs/api-reference/next/streaming.md b/docs/api-reference/next/streaming.md
index 90b651435677..86a6ade3c4fd 100644
--- a/docs/api-reference/next/streaming.md
+++ b/docs/api-reference/next/streaming.md
@@ -6,54 +6,6 @@ description: Streaming related APIs to build Next.js apps in streaming SSR or wi
The experimental `next/streaming` module provides streaming related APIs to port the existing functionality of Next.js apps to streaming scenarios and facilitate the usage of React Server Components.
-## unstable_useWebVitalsReport
-
-Next.js provides an `_app` component-level function, [`reportWebVitals`](docs/advanced-features/measuring-performance), for tracking performance metrics. With Server Components, you may have a pure server-side custom `_app` component (which doesn't run client effects) so the existing API won't work.
-
-With the new `unstable_useWebVitalsReport` API, you're able to track [Core Web Vitals](https://nextjs.org/learn/seo/web-performance) in client components:
-
-```jsx
-// pages/_app.js
-import { unstable_useWebVitalsReport } from 'next/streaming'
-
-export default function Home() {
- unstable_useWebVitalsReport((data) => {
- console.log(data)
- })
-
- return
Home Page
-}
-```
-
-This method could also be used to replace statically exported `reportWebVitals` functions in your existing `_app`:
-
-```jsx
-// pages/_app.server.js
-import Layout from '../components/layout.client.js'
-
-export default function App({ children }) {
- return {children}
-}
-```
-
-```jsx
-// components/layout.client.js
-import { unstable_useWebVitalsReport } from 'next/streaming'
-
-export default function Layout() {
- unstable_useWebVitalsReport((data) => {
- console.log(data)
- })
-
- return (
-
- )
-}
-```
-
## unstable_useRefreshRoot
Since Server Components are rendered on the server-side, in some cases you might need to partially refresh content from the server.
diff --git a/errors/client-flush-effects.md b/errors/client-flush-effects.md
deleted file mode 100644
index 18ca8dd126cc..000000000000
--- a/errors/client-flush-effects.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# `unstable_useFlushEffects` can not be called on the client
-
-#### Why This Error Occurred
-
-The `unstable_useFlushEffects` hook was executed while rendering a component on the client, or in another unsupported environment.
-
-#### Possible Ways to Fix It
-
-The `unstable_useFlushEffects` hook can only be called while _server rendering a client component_. As a best practice, we recommend creating a wrapper hook:
-
-```jsx
-// lib/use-style-libraries.js
-import { unstable_useFlushEffects as useFlushEffects } from 'next/streaming'
-
-export default function useStyleLibraries() {
- if (typeof window === 'undefined') {
- // eslint-disable-next-line react-hooks/rules-of-hooks
- useFlushEffects([
- /* ... */
- ])
- }
- /* ... */
-}
-```
diff --git a/errors/manifest.json b/errors/manifest.json
index 8e5375a6d5a1..00ec91db0c7f 100644
--- a/errors/manifest.json
+++ b/errors/manifest.json
@@ -638,14 +638,6 @@
"title": "opening-an-issue",
"path": "/errors/opening-an-issue.md"
},
- {
- "title": "multiple-flush-effects",
- "path": "/errors/multiple-flush-effects.md"
- },
- {
- "title": "client-flush-effects",
- "path": "/errors/client-flush-effects.md"
- },
{
"title": "import-next",
"path": "/errors/import-next.md"
diff --git a/errors/multiple-flush-effects.md b/errors/multiple-flush-effects.md
deleted file mode 100644
index b19e88edd04b..000000000000
--- a/errors/multiple-flush-effects.md
+++ /dev/null
@@ -1,9 +0,0 @@
-# The `unstable_useFlushEffects` hook cannot be used more than once.
-
-#### Why This Error Occurred
-
-The `unstable_useFlushEffects` hook is being used more than once while a page is being rendered.
-
-#### Possible Ways to Fix It
-
-The `unstable_useFlushEffects` hook should only be called inside the body of the `pages/_app` component, before returning any `` boundaries. Restructure your application to prevent extraneous calls.
diff --git a/packages/next/client/index.tsx b/packages/next/client/index.tsx
index f966b50224db..662ba6b8bd14 100644
--- a/packages/next/client/index.tsx
+++ b/packages/next/client/index.tsx
@@ -32,10 +32,6 @@ import measureWebVitals from './performance-relayer'
import { RouteAnnouncer } from './route-announcer'
import { createRouter, makePublicRouterInstance } from './router'
import { getProperError } from '../lib/is-error'
-import {
- flushBufferedVitalsMetrics,
- trackWebVitalMetric,
-} from './streaming/vitals'
import { RefreshContext } from './streaming/refresh'
import { ImageConfigContext } from '../shared/lib/image-config-context'
import { ImageConfigComplete } from '../shared/lib/image-config'
@@ -291,38 +287,38 @@ export async function hydrate(opts?: { beforeRender?: () => Promise }) {
const { component: app, exports: mod } = appEntrypoint
CachedApp = app as AppComponent
- const exportedReportWebVitals = mod && mod.reportWebVitals
- onPerfEntry = ({
- id,
- name,
- startTime,
- value,
- duration,
- entryType,
- entries,
- }: any): void => {
- // Combines timestamp with random number for unique ID
- const uniqueID: string = `${Date.now()}-${
- Math.floor(Math.random() * (9e12 - 1)) + 1e12
- }`
- let perfStartEntry: string | undefined
-
- if (entries && entries.length) {
- perfStartEntry = entries[0].startTime
- }
-
- const webVitals: NextWebVitalsMetric = {
- id: id || uniqueID,
+ if (mod && mod.reportWebVitals) {
+ onPerfEntry = ({
+ id,
name,
- startTime: startTime || perfStartEntry,
- value: value == null ? duration : value,
- label:
- entryType === 'mark' || entryType === 'measure'
- ? 'custom'
- : 'web-vital',
+ startTime,
+ value,
+ duration,
+ entryType,
+ entries,
+ }: any): void => {
+ // Combines timestamp with random number for unique ID
+ const uniqueID: string = `${Date.now()}-${
+ Math.floor(Math.random() * (9e12 - 1)) + 1e12
+ }`
+ let perfStartEntry: string | undefined
+
+ if (entries && entries.length) {
+ perfStartEntry = entries[0].startTime
+ }
+
+ const webVitals: NextWebVitalsMetric = {
+ id: id || uniqueID,
+ name,
+ startTime: startTime || perfStartEntry,
+ value: value == null ? duration : value,
+ label:
+ entryType === 'mark' || entryType === 'measure'
+ ? 'custom'
+ : 'web-vital',
+ }
+ mod.reportWebVitals(webVitals)
}
- exportedReportWebVitals?.(webVitals)
- trackWebVitalMetric(webVitals)
}
const pageEntrypoint =
@@ -1033,8 +1029,6 @@ function Root({
// don't cause any hydration delay:
React.useEffect(() => {
measureWebVitals(onPerfEntry)
-
- flushBufferedVitalsMetrics()
}, [])
if (process.env.__NEXT_TEST_MODE) {
diff --git a/packages/next/client/streaming/index.ts b/packages/next/client/streaming/index.ts
index d072e85795ae..566fab06ab74 100644
--- a/packages/next/client/streaming/index.ts
+++ b/packages/next/client/streaming/index.ts
@@ -1,3 +1 @@
export { useRefreshRoot as unstable_useRefreshRoot } from './refresh'
-export { useWebVitalsReport as unstable_useWebVitalsReport } from './vitals'
-export { useFlushEffects as unstable_useFlushEffects } from '../../shared/lib/flush-effects'
diff --git a/packages/next/client/streaming/vitals.ts b/packages/next/client/streaming/vitals.ts
deleted file mode 100644
index e239470457a5..000000000000
--- a/packages/next/client/streaming/vitals.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import { useEffect, useRef } from 'react'
-import { NextWebVitalsMetric } from '../../pages/_app'
-
-type ReportWebVitalsCallback = (webVitals: NextWebVitalsMetric) => any
-export const webVitalsCallbacks = new Set()
-
-let flushed = false
-const metrics: NextWebVitalsMetric[] = []
-
-export function getBufferedVitalsMetrics() {
- return metrics
-}
-
-export function flushBufferedVitalsMetrics() {
- flushed = true
- metrics.length = 0
-}
-
-export function trackWebVitalMetric(metric: NextWebVitalsMetric) {
- metrics.push(metric)
- webVitalsCallbacks.forEach((callback) => callback(metric))
-}
-
-export function useWebVitalsReport(callback: ReportWebVitalsCallback) {
- const metricIndexRef = useRef(0)
-
- if (process.env.NODE_ENV === 'development') {
- if (flushed) {
- console.error(
- 'The `useWebVitalsReport` hook was called too late -- did you use it inside of a boundary?'
- )
- }
- }
-
- useEffect(() => {
- // Flush calculated metrics
- const reportMetric = (metric: NextWebVitalsMetric) => {
- callback(metric)
- metricIndexRef.current = metrics.length
- }
- for (let i = metricIndexRef.current; i < metrics.length; i++) {
- reportMetric(metrics[i])
- }
-
- webVitalsCallbacks.add(reportMetric)
- return () => {
- webVitalsCallbacks.delete(reportMetric)
- }
- }, [callback])
-}
diff --git a/packages/next/server/render.tsx b/packages/next/server/render.tsx
index 788cb8571c92..d4935d10b3da 100644
--- a/packages/next/server/render.tsx
+++ b/packages/next/server/render.tsx
@@ -79,7 +79,6 @@ import {
continueFromInitialStream,
} from './node-web-streams-helper'
import { ImageConfigContext } from '../shared/lib/image-config-context'
-import { FlushEffectsContext } from '../shared/lib/flush-effects'
import { interopDefault } from '../lib/interop-default'
import stripAnsi from 'next/dist/compiled/strip-ansi'
import { urlQueryToSearchParams } from '../shared/lib/router/utils/querystring'
@@ -794,62 +793,33 @@ export async function renderToHTML(
return <>{styles}>
}
- let flushEffects: Array<() => React.ReactNode> | null = null
- function FlushEffectContainer({ children }: { children: JSX.Element }) {
- // If the client tree suspends, this component will be rendered multiple
- // times before we flush. To ensure we don't call old callbacks corresponding
- // to a previous render, we clear any registered callbacks whenever we render.
- flushEffects = null
-
- const flushEffectsImpl = React.useCallback(
- (callbacks: Array<() => React.ReactNode>) => {
- if (flushEffects) {
- throw new Error(
- 'The `useFlushEffects` hook cannot be used more than once.' +
- '\nRead more: https://nextjs.org/docs/messages/multiple-flush-effects'
- )
- }
- flushEffects = callbacks
- },
- []
- )
-
- return (
-
- {children}
-
- )
- }
-
const AppContainer = ({ children }: { children: JSX.Element }) => (
-
-
-
- {
- head = state
- },
- updateScripts: (scripts) => {
- scriptLoader = scripts
- },
- scripts: initialScripts,
- mountedInstances: new Set(),
- }}
+
+
+ {
+ head = state
+ },
+ updateScripts: (scripts) => {
+ scriptLoader = scripts
+ },
+ scripts: initialScripts,
+ mountedInstances: new Set(),
+ }}
+ >
+ reactLoadableModules.push(moduleName)}
>
- reactLoadableModules.push(moduleName)}
- >
-
-
- {children}
-
-
-
-
-
-
-
+
+
+ {children}
+
+
+
+
+
+
)
// The `useId` API uses the path indexes to generate an ID for each node.
@@ -1480,17 +1450,7 @@ export async function renderToHTML(
// this must be called inside bodyResult so appWrappers is
// up to date when `wrapApp` is called
const flushEffectHandler = (): string => {
- const allFlushEffects = [
- styledJsxFlushEffect,
- ...(flushEffects || []),
- ]
- const flushed = ReactDOMServer.renderToString(
- <>
- {allFlushEffects.map((flushEffect, i) => (
- {flushEffect()}
- ))}
- >
- )
+ const flushed = ReactDOMServer.renderToString(styledJsxFlushEffect())
return flushed
}
diff --git a/packages/next/server/view-render.tsx b/packages/next/server/view-render.tsx
index 8127aef1675e..8fbdaafdec75 100644
--- a/packages/next/server/view-render.tsx
+++ b/packages/next/server/view-render.tsx
@@ -16,7 +16,6 @@ import {
createBufferedTransformStream,
continueFromInitialStream,
} from './node-web-streams-helper'
-import { FlushEffectsContext } from '../shared/lib/flush-effects'
import { isDynamicRoute } from '../shared/lib/router/utils'
import { tryGetPreviewData } from './api-utils/node'
@@ -389,37 +388,8 @@ export async function renderToHTML(
return <>{styles}>
}
- let flushEffects: Array<() => React.ReactNode> | null = null
- function FlushEffectContainer({ children }: { children: JSX.Element }) {
- // If the client tree suspends, this component will be rendered multiple
- // times before we flush. To ensure we don't call old callbacks corresponding
- // to a previous render, we clear any registered callbacks whenever we render.
- flushEffects = null
-
- const flushEffectsImpl = React.useCallback(
- (callbacks: Array<() => React.ReactNode>) => {
- if (flushEffects) {
- throw new Error(
- 'The `useFlushEffects` hook cannot be used more than once.' +
- '\nRead more: https://nextjs.org/docs/messages/multiple-flush-effects'
- )
- }
- flushEffects = callbacks
- },
- []
- )
-
- return (
-
- {children}
-
- )
- }
-
const AppContainer = ({ children }: { children: JSX.Element }) => (
-
- {children}
-
+ {children}
)
const renderServerComponentData = isFlight
@@ -462,14 +432,7 @@ export async function renderToHTML(
})
const flushEffectHandler = (): string => {
- const allFlushEffects = [styledJsxFlushEffect, ...(flushEffects || [])]
- const flushed = ReactDOMServer.renderToString(
- <>
- {allFlushEffects.map((flushEffect, i) => (
- {flushEffect()}
- ))}
- >
- )
+ const flushed = ReactDOMServer.renderToString(styledJsxFlushEffect())
return flushed
}
diff --git a/packages/next/shared/lib/flush-effects.ts b/packages/next/shared/lib/flush-effects.ts
deleted file mode 100644
index 341c126ed9e6..000000000000
--- a/packages/next/shared/lib/flush-effects.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { createContext, useContext } from 'react'
-
-export type FlushEffectsHook = (callbacks: Array<() => React.ReactNode>) => void
-
-export const FlushEffectsContext: React.Context =
- createContext(null as any)
-
-export function useFlushEffects(callbacks: Array<() => React.ReactNode>): void {
- const flushEffectsImpl = useContext(FlushEffectsContext)
- if (!flushEffectsImpl) {
- throw new Error(
- `useFlushEffects can not be called on the client.` +
- `\nRead more: https://nextjs.org/docs/messages/client-flush-effects`
- )
- }
- return flushEffectsImpl(callbacks)
-}
-
-if (process.env.NODE_ENV !== 'production') {
- FlushEffectsContext.displayName = 'FlushEffectsContext'
-}
diff --git a/test/integration/react-18/app/pages/use-flush-effect/client.js b/test/integration/react-18/app/pages/use-flush-effect/client.js
deleted file mode 100644
index 884d7f9266ec..000000000000
--- a/test/integration/react-18/app/pages/use-flush-effect/client.js
+++ /dev/null
@@ -1,36 +0,0 @@
-import { unstable_useFlushEffects } from 'next/streaming'
-import React from 'react'
-
-class ErrorBoundary extends React.Component {
- state = {}
-
- static getDerivedStateFromError(error) {
- return { error }
- }
-
- render() {
- return this.state.error ? (
- {this.state.error.message}
- ) : (
- this.props.children
- )
- }
-}
-
-function Component() {
- unstable_useFlushEffects([])
- return null
-}
-
-export default function Client() {
- return (
-
-
-
- )
-}
-
-export async function getServerSideProps() {
- // disable exporting this page
- return { props: {} }
-}
diff --git a/test/integration/react-18/app/pages/use-flush-effect/custom.js b/test/integration/react-18/app/pages/use-flush-effect/custom.js
deleted file mode 100644
index f8d09da3462d..000000000000
--- a/test/integration/react-18/app/pages/use-flush-effect/custom.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import { unstable_useFlushEffects } from 'next/streaming'
-
-export default function Custom() {
- if (typeof window === 'undefined') {
- // eslint-disable-next-line react-hooks/rules-of-hooks
- unstable_useFlushEffects([
- () => foo,
- () => bar,
- ])
- }
- return null
-}
diff --git a/test/integration/react-18/app/pages/use-flush-effect/multiple-calls.js b/test/integration/react-18/app/pages/use-flush-effect/multiple-calls.js
deleted file mode 100644
index 5417eac3457a..000000000000
--- a/test/integration/react-18/app/pages/use-flush-effect/multiple-calls.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import { unstable_useFlushEffects } from 'next/streaming'
-
-function Component() {
- if (typeof window === 'undefined') {
- // eslint-disable-next-line react-hooks/rules-of-hooks
- unstable_useFlushEffects([])
- }
- return null
-}
-
-export default function MultipleCalls() {
- return (
- <>
-
-
- >
- )
-}
-
-export async function getServerSideProps() {
- // disable exporting this page
- return { props: {} }
-}
diff --git a/test/integration/react-18/test/concurrent.js b/test/integration/react-18/test/concurrent.js
index f849a0a80c94..a1b3c3a8da71 100644
--- a/test/integration/react-18/test/concurrent.js
+++ b/test/integration/react-18/test/concurrent.js
@@ -16,22 +16,6 @@ export default (context, _render) => {
}
}
- it('throws if useFlushEffects is used more than once', async () => {
- await renderViaHTTP(context.appPort, '/use-flush-effect/multiple-calls')
- expect(context.stderr).toContain(
- 'Error: The `useFlushEffects` hook cannot be used more than once.'
- )
- })
-
- it('throws if useFlushEffects is called on the client', async () => {
- await withBrowser('/use-flush-effect/client', async (browser) => {
- await check(
- () => browser.waitForElementByCss('#error').text(),
- /useFlushEffects can not be called on the client/
- )
- })
- })
-
it('flushes styled-jsx styles as the page renders', async () => {
const html = await renderViaHTTP(
context.appPort,
@@ -51,17 +35,4 @@ export default (context, _render) => {
)
})
})
-
- it('flushes custom effects', async () => {
- await withBrowser('/use-flush-effect/custom', async (browser) => {
- await check(
- () => browser.waitForElementByCss('#custom-flush-effect-1').text(),
- /foo/
- )
- await check(
- () => browser.waitForElementByCss('#custom-flush-effect-2').text(),
- /bar/
- )
- })
- })
}
diff --git a/test/integration/relay-analytics-disabled/pages/index.js b/test/integration/relay-analytics-disabled/pages/index.js
index d8064b2d5b1a..46c828a80fd0 100644
--- a/test/integration/relay-analytics-disabled/pages/index.js
+++ b/test/integration/relay-analytics-disabled/pages/index.js
@@ -1,5 +1,3 @@
-import { getBufferedVitalsMetrics } from 'next/dist/client/streaming/vitals'
-
if (typeof navigator !== 'undefined') {
window.__BEACONS = window.__BEACONS || []
@@ -22,7 +20,6 @@ export default () => {
Foo!
bar!
-
{`buffered metrics: ${getBufferedVitalsMetrics().length}`}
)
}
diff --git a/test/integration/relay-analytics-disabled/test/index.test.js b/test/integration/relay-analytics-disabled/test/index.test.js
index 0994f67ddb15..f7dc28e64799 100644
--- a/test/integration/relay-analytics-disabled/test/index.test.js
+++ b/test/integration/relay-analytics-disabled/test/index.test.js
@@ -30,13 +30,11 @@ describe('Analytics relayer (disabled)', () => {
const browser = await webdriver(appPort, '/')
await browser.waitForElementByCss('h1')
const h1Text = await browser.elementByCss('h1').text()
- const pText = await browser.elementByCss('p').text()
const firstContentfulPaint = parseFloat(
await browser.eval('localStorage.getItem("FCP")')
)
expect(h1Text).toMatch(/Foo!/)
- expect(pText).toMatch('buffered metrics: 0')
expect(firstContentfulPaint).not.toBeNaN()
expect(firstContentfulPaint).toBeGreaterThan(0)
diff --git a/test/integration/relay-analytics/pages/_app.js b/test/integration/relay-analytics/pages/_app.js
index bbe74d5e1f87..44f936f124f0 100644
--- a/test/integration/relay-analytics/pages/_app.js
+++ b/test/integration/relay-analytics/pages/_app.js
@@ -8,8 +8,6 @@ export default class MyApp extends App {}
Method is experimental and will eventually be handled in a Next.js plugin
*/
-// Below comment will be used for replacing exported report method with hook based one.
-///* reportWebVitals
export function reportWebVitals(data) {
const name = data.name || data.entryType
localStorage.setItem(
@@ -19,4 +17,3 @@ export function reportWebVitals(data) {
const countMap = window.__BEACONS_COUNT
countMap.set(name, (countMap.get(name) || 0) + 1)
}
-// reportWebVitals */
diff --git a/test/integration/relay-analytics/pages/index.js b/test/integration/relay-analytics/pages/index.js
index 82b506fcc7bc..cf1d4664d88a 100644
--- a/test/integration/relay-analytics/pages/index.js
+++ b/test/integration/relay-analytics/pages/index.js
@@ -1,7 +1,3 @@
-/* global localStorage */
-import { unstable_useWebVitalsReport } from 'next/streaming'
-import { getBufferedVitalsMetrics } from 'next/dist/client/streaming/vitals'
-
if (typeof navigator !== 'undefined') {
window.__BEACONS = window.__BEACONS || []
window.__BEACONS_COUNT = new Map()
@@ -22,23 +18,10 @@ if (typeof navigator !== 'undefined') {
export default () => {
// Below comment will be used for replacing exported report method with hook based one.
- ///* unstable_useWebVitalsReport
- unstable_useWebVitalsReport((data) => {
- const name = data.name || data.entryType
- localStorage.setItem(
- name,
- data.value !== undefined ? data.value : data.startTime
- )
- const countMap = window.__BEACONS_COUNT
- countMap.set(name, (countMap.get(name) || 0) + 1)
- })
- // unstable_useWebVitalsReport */
-
return (
Foo!
bar!
-
{`buffered metrics: ${getBufferedVitalsMetrics().length}`}
)
}
diff --git a/test/integration/relay-analytics/test/index.test.js b/test/integration/relay-analytics/test/index.test.js
index 4a5f6537a967..d4140e36706d 100644
--- a/test/integration/relay-analytics/test/index.test.js
+++ b/test/integration/relay-analytics/test/index.test.js
@@ -2,18 +2,9 @@
import { join } from 'path'
import webdriver from 'next-webdriver'
-import {
- File,
- killApp,
- findPort,
- nextBuild,
- nextStart,
- check,
-} from 'next-test-utils'
+import { killApp, findPort, nextBuild, nextStart, check } from 'next-test-utils'
const appDir = join(__dirname, '../')
-const customApp = new File(join(appDir, 'pages/_app.js'))
-const indexPage = new File(join(appDir, 'pages/index.js'))
let appPort
let server
@@ -33,31 +24,8 @@ async function killServer() {
}
describe('Analytics relayer with exported method', () => {
- beforeAll(async () => {
- // Keep app exported reporting and comment the hook one
- indexPage.replace('///* unstable_useWebVitalsReport', '/*')
- indexPage.replace('// unstable_useWebVitalsReport */', '*/')
- await buildApp()
- })
- afterAll(async () => {
- indexPage.restore()
- await killServer()
- })
- runTest()
-})
-
-describe('Analytics relayer with hook', () => {
- beforeAll(async () => {
- // Keep hook reporting and comment the app exported one
- customApp.replace('///* reportWebVitals', '/*')
- customApp.replace('// reportWebVitals */', '*/')
- await buildApp()
- })
-
- afterAll(async () => {
- customApp.restore()
- await killServer()
- })
+ beforeAll(async () => await buildApp())
+ afterAll(async () => await killServer())
runTest()
})
@@ -67,7 +35,6 @@ function runTest() {
await browser.waitForElementByCss('h1')
const h1Text = await browser.elementByCss('h1').text()
- const pText = await browser.elementByCss('p').text()
const data = parseFloat(
await browser.eval('localStorage.getItem("Next.js-hydration")')
)
@@ -82,7 +49,6 @@ function runTest() {
)
let cls = await browser.eval('localStorage.getItem("CLS")')
expect(h1Text).toMatch(/Foo!/)
- expect(pText).toMatch('buffered metrics: 0')
expect(data).not.toBeNaN()
expect(data).toBeGreaterThan(0)
expect(firstByte).not.toBeNaN()