Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drop the unstable web vital hook and remove exports of flush effects #36912

Merged
merged 5 commits into from May 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
48 changes: 0 additions & 48 deletions docs/api-reference/next/streaming.md
Expand Up @@ -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 <div>Home Page</div>
}
```

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 <Layout>{children}</Layout>
}
```

```jsx
// components/layout.client.js
import { unstable_useWebVitalsReport } from 'next/streaming'

export default function Layout() {
unstable_useWebVitalsReport((data) => {
console.log(data)
})

return (
<div className="container">
<h1>Hello</h1>
<div className="main">{children}</div>
</div>
)
}
```

## unstable_useRefreshRoot

Since Server Components are rendered on the server-side, in some cases you might need to partially refresh content from the server.
Expand Down
24 changes: 0 additions & 24 deletions errors/client-flush-effects.md

This file was deleted.

8 changes: 0 additions & 8 deletions errors/manifest.json
Expand Up @@ -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"
Expand Down
9 changes: 0 additions & 9 deletions errors/multiple-flush-effects.md

This file was deleted.

66 changes: 30 additions & 36 deletions packages/next/client/index.tsx
Expand Up @@ -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'
Expand Down Expand Up @@ -291,38 +287,38 @@ export async function hydrate(opts?: { beforeRender?: () => Promise<void> }) {

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 =
Expand Down Expand Up @@ -1033,8 +1029,6 @@ function Root({
// don't cause any hydration delay:
React.useEffect(() => {
measureWebVitals(onPerfEntry)

flushBufferedVitalsMetrics()
}, [])

if (process.env.__NEXT_TEST_MODE) {
Expand Down
2 changes: 0 additions & 2 deletions 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'
50 changes: 0 additions & 50 deletions packages/next/client/streaming/vitals.ts

This file was deleted.

92 changes: 26 additions & 66 deletions packages/next/server/render.tsx
Expand Up @@ -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'
Expand Down Expand Up @@ -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 (
<FlushEffectsContext.Provider value={flushEffectsImpl}>
{children}
</FlushEffectsContext.Provider>
)
}

const AppContainer = ({ children }: { children: JSX.Element }) => (
<FlushEffectContainer>
<RouterContext.Provider value={router}>
<AmpStateContext.Provider value={ampState}>
<HeadManagerContext.Provider
value={{
updateHead: (state) => {
head = state
},
updateScripts: (scripts) => {
scriptLoader = scripts
},
scripts: initialScripts,
mountedInstances: new Set(),
}}
<RouterContext.Provider value={router}>
<AmpStateContext.Provider value={ampState}>
<HeadManagerContext.Provider
value={{
updateHead: (state) => {
head = state
},
updateScripts: (scripts) => {
scriptLoader = scripts
},
scripts: initialScripts,
mountedInstances: new Set(),
}}
>
<LoadableContext.Provider
value={(moduleName) => reactLoadableModules.push(moduleName)}
>
<LoadableContext.Provider
value={(moduleName) => reactLoadableModules.push(moduleName)}
>
<StyleRegistry registry={jsxStyleRegistry}>
<ImageConfigContext.Provider value={images}>
{children}
</ImageConfigContext.Provider>
</StyleRegistry>
</LoadableContext.Provider>
</HeadManagerContext.Provider>
</AmpStateContext.Provider>
</RouterContext.Provider>
</FlushEffectContainer>
<StyleRegistry registry={jsxStyleRegistry}>
<ImageConfigContext.Provider value={images}>
{children}
</ImageConfigContext.Provider>
</StyleRegistry>
</LoadableContext.Provider>
</HeadManagerContext.Provider>
</AmpStateContext.Provider>
</RouterContext.Provider>
)

// The `useId` API uses the path indexes to generate an ID for each node.
Expand Down Expand Up @@ -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) => (
<React.Fragment key={i}>{flushEffect()}</React.Fragment>
))}
</>
)
const flushed = ReactDOMServer.renderToString(styledJsxFlushEffect())
return flushed
}

Expand Down