Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Separate next/dynamic implementation for app and pages (#45565)
## Issue To address the problem that we introduced in 13.0.7 (#42589) where we thought we could use same implementation `next/dynamic` for both `pages/` and `app/` directory. But it turns out it leads to many problems, such as: * SSR preloading could miss the content, especially with nested dynamic calls * Closes #45213 * Introducing suspense boundary into `next/dynamic` with extra wrapped `<Suspense>` outside will lead to content is not resolevd during SSR * Related #45151 * Closes #45099 * Unexpected hydration errors for suspense boundaries. Though react removed this error but the 18.3 is not out yet. * Closes #44083 * Closes #45246 ## Solution Separate the dynamic implementation for `app/` dir and `pages/`. For `app/` dir we can encourage users to: * Directly use `React.lazy` + `Suspense` for SSR'd content, and `next/dynamic` * For non SSR components since it requires some internal integeration with next.js. For `pages/` dir we still keep the original implementation If you want to use `<Suspense>` with dynamic `fallback` value, use `React.lazy` + `Suspense` directly instead of picking up `next/dynamic` * Closes #45116 This will solve various issue before react 18.3 is out and let users still progressively upgrade to new versions of next.js. ## Bug Fix - [x] Related issues linked using `fixes #number` - [x] Integration tests added - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md)
- Loading branch information
Showing
34 changed files
with
257 additions
and
87 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
packages/next/src/client/components/bailout-to-client-rendering.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
import React from 'react' | ||
import Loadable from './loadable' | ||
|
||
type ComponentModule<P = {}> = { default: React.ComponentType<P> } | ||
|
||
export declare type LoaderComponent<P = {}> = Promise< | ||
React.ComponentType<P> | ComponentModule<P> | ||
> | ||
|
||
export declare type Loader<P = {}> = () => LoaderComponent<P> | ||
|
||
export type LoaderMap = { [module: string]: () => Loader<any> } | ||
|
||
export type LoadableGeneratedOptions = { | ||
webpack?(): any | ||
modules?(): LoaderMap | ||
} | ||
|
||
export type DynamicOptionsLoadingProps = { | ||
error?: Error | null | ||
isLoading?: boolean | ||
pastDelay?: boolean | ||
retry?: () => void | ||
timedOut?: boolean | ||
} | ||
|
||
// Normalize loader to return the module as form { default: Component } for `React.lazy`. | ||
// Also for backward compatible since next/dynamic allows to resolve a component directly with loader | ||
// Client component reference proxy need to be converted to a module. | ||
function convertModule<P>(mod: React.ComponentType<P> | ComponentModule<P>) { | ||
return { default: (mod as ComponentModule<P>)?.default || mod } | ||
} | ||
|
||
export type DynamicOptions<P = {}> = LoadableGeneratedOptions & { | ||
loading?: (loadingProps: DynamicOptionsLoadingProps) => JSX.Element | null | ||
loader?: Loader<P> | ||
loadableGenerated?: LoadableGeneratedOptions | ||
ssr?: boolean | ||
} | ||
|
||
export type LoadableOptions<P = {}> = DynamicOptions<P> | ||
|
||
export type LoadableFn<P = {}> = ( | ||
opts: LoadableOptions<P> | ||
) => React.ComponentType<P> | ||
|
||
export type LoadableComponent<P = {}> = React.ComponentType<P> | ||
|
||
export default function dynamic<P = {}>( | ||
dynamicOptions: DynamicOptions<P> | Loader<P>, | ||
options?: DynamicOptions<P> | ||
): React.ComponentType<P> { | ||
const loadableFn: LoadableFn<P> = Loadable | ||
|
||
const loadableOptions: LoadableOptions<P> = { | ||
// A loading component is not required, so we default it | ||
loading: ({ error, isLoading, pastDelay }) => { | ||
if (!pastDelay) return null | ||
if (process.env.NODE_ENV !== 'production') { | ||
if (isLoading) { | ||
return null | ||
} | ||
if (error) { | ||
return ( | ||
<p> | ||
{error.message} | ||
<br /> | ||
{error.stack} | ||
</p> | ||
) | ||
} | ||
} | ||
return null | ||
}, | ||
} | ||
|
||
if (typeof dynamicOptions === 'function') { | ||
loadableOptions.loader = dynamicOptions | ||
} | ||
|
||
Object.assign(loadableOptions, options) | ||
|
||
const loaderFn = loadableOptions.loader as () => LoaderComponent<P> | ||
const loader = () => | ||
loaderFn != null | ||
? loaderFn().then(convertModule) | ||
: Promise.resolve(convertModule(() => null)) | ||
|
||
return loadableFn({ ...loadableOptions, loader: loader as Loader<P> }) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import React from 'react' | ||
import { NoSSR } from './dynamic-no-ssr' | ||
|
||
function Loadable(options: any) { | ||
const opts = Object.assign( | ||
{ | ||
loader: null, | ||
loading: null, | ||
ssr: true, | ||
}, | ||
options | ||
) | ||
|
||
opts.lazy = React.lazy(opts.loader) | ||
|
||
function LoadableComponent(props: any) { | ||
const Loading = opts.loading | ||
const fallbackElement = ( | ||
<Loading isLoading={true} pastDelay={true} error={null} /> | ||
) | ||
|
||
const Wrap = opts.ssr ? React.Fragment : NoSSR | ||
const Lazy = opts.lazy | ||
|
||
return ( | ||
<React.Suspense fallback={fallbackElement}> | ||
<Wrap> | ||
<Lazy {...props} /> | ||
</Wrap> | ||
</React.Suspense> | ||
) | ||
} | ||
|
||
LoadableComponent.displayName = 'LoadableComponent' | ||
|
||
return LoadableComponent | ||
} | ||
|
||
export default Loadable |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.