Skip to content

Commit

Permalink
Drop the unstable web vital hook and remove exports of flush effects (#…
Browse files Browse the repository at this point in the history
…36912)

* remove the experimental web vital hook api
* remove the exported flush effects api and only error on development, keep only usage to styled-jsx

for web vital hook API: The usage is not widly adopted since the existing exported vital api could do the same work. In the future we'll deprecate the `_app.server` in favor of `_app` in server component pages. so that this api won't be required.

for flush effects api: other css-in-js libs are not using the same approach like styled-jsx which holding a style registry and could flush it during streaming. emotion-js and styled-components are still relying on `Document.getInitialProps` atm and we have supported it in latest canary
  • Loading branch information
huozhi committed May 14, 2022
1 parent fa4ca66 commit ed4d009
Show file tree
Hide file tree
Showing 19 changed files with 61 additions and 465 deletions.
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

0 comments on commit ed4d009

Please sign in to comment.