diff --git a/packages/next/shared/lib/dynamic.tsx b/packages/next/shared/lib/dynamic.tsx index 99248d3ee6f64ef..baee4843ef12af9 100644 --- a/packages/next/shared/lib/dynamic.tsx +++ b/packages/next/shared/lib/dynamic.tsx @@ -130,18 +130,10 @@ export default function dynamic

( } const { suspense, ssr, loader, loadableGenerated } = loadableOptions - // client side rendering - const csr = ssr === false delete loadableOptions.ssr delete loadableOptions.loadableGenerated if (typeof loadableOptions.loader === 'function' && suspense) { - // If `suspense = true` and `ssr = false`: - // render empty on server side; - // render lazy component on client side. - if (csr && isServerSide) { - return () => null - } loadableOptions.loader = React.lazy( loader as () => Promise<{ default: React.ComponentType

@@ -158,7 +150,7 @@ export default function dynamic

( } // support for disabling server side rendering, eg: dynamic(import('../hello-world'), {ssr: false}) - if (csr && !suspense) { + if (ssr === false && !suspense) { return noSSR(loadableFn, loadableOptions) } diff --git a/test/integration/react-18/prerelease/components/dynamic-suspense.js b/test/integration/react-18/prerelease/components/dynamic-suspense.js index 94977959d2ad458..740ebe411ffca04 100644 --- a/test/integration/react-18/prerelease/components/dynamic-suspense.js +++ b/test/integration/react-18/prerelease/components/dynamic-suspense.js @@ -2,8 +2,8 @@ import { Suspense } from 'react' import dynamic from 'next/dynamic' const Hello = dynamic(() => import('./hello'), { - ssr: false, - suspense: false, + ssr: true, + suspense: true, }) export default function SuspenseNoSSR({ thrown }) { diff --git a/test/integration/react-18/prerelease/components/hello.js b/test/integration/react-18/prerelease/components/hello.js index 68f35cf0ebb77d8..b9c423d7937ea37 100644 --- a/test/integration/react-18/prerelease/components/hello.js +++ b/test/integration/react-18/prerelease/components/hello.js @@ -4,7 +4,7 @@ import ReactDOM from 'react-dom' export default function Hello({ thrown = false }) { // only throw on server side render if (thrown && typeof window === 'undefined') { - throw new Promise((resolve) => setTimeout(resolve, 500)) + throw new Promise((resolve) => setTimeout(resolve, 300)) } return

hello {ReactDOM.version}

} diff --git a/test/integration/react-18/test/dynamic.js b/test/integration/react-18/test/dynamic.js index e99e56178023aa7..2deea643c6ad345 100644 --- a/test/integration/react-18/test/dynamic.js +++ b/test/integration/react-18/test/dynamic.js @@ -35,9 +35,9 @@ export default (context, render) => { describe('suspense:true option', () => { describe('promise is thrown on server side', () => { - beforeAll(() => { - writeComponent({ suspense: true }) - }) + // let `ssr` option be auto overridden + beforeAll(() => writeComponent({ suspense: true })) + afterAll(() => page.restore()) it('should render the fallback on server side', async () => { const $ = await get$('/suspense/thrown') @@ -62,6 +62,7 @@ export default (context, render) => { }) describe('promise is not thrown on server side', () => { + afterAll(() => page.restore()) it('should render fallback on server side', async () => { const $ = await get$('/suspense/no-thrown') const text = $('#__next').text() @@ -83,18 +84,17 @@ export default (context, render) => { }) describe('suspense:false option', () => { - it('should render the fallback on server side', async () => { - writeComponent({ suspense: false, ssr: false }) + beforeAll(() => writeComponent({ suspense: false, ssr: false })) + afterAll(() => page.restore()) + + it('should render nothing on server side', async () => { const $ = await get$('/suspense/thrown') - const html = $('body').html() - expect(html).toContain('loading') - expect(JSON.parse($('#__NEXT_DATA__').html()).dynamicIds).toContain( - './components/hello.js' - ) + const text = $('#__next').text() + expect(text).toBe('') + expect(JSON.parse($('#__NEXT_DATA__').html()).dynamicIds).toBeUndefined() }) it('should hydrate suspenses on client side with ssr disabled', async () => { - writeComponent({ suspense: false, ssr: false }) let browser try { browser = await webdriver(context.appPort, '/suspense/no-thrown') diff --git a/test/integration/react-18/test/index.test.js b/test/integration/react-18/test/index.test.js index e2f94bddfea156c..5431298f784fea0 100644 --- a/test/integration/react-18/test/index.test.js +++ b/test/integration/react-18/test/index.test.js @@ -10,6 +10,7 @@ import { launchApp, nextBuild, nextStart, + fetchViaHTTP, renderViaHTTP, } from 'next-test-utils' import dynamic from './dynamic' @@ -91,53 +92,55 @@ describe('React 18 Support', () => { }) afterAll(async () => await fs.remove(join(appDir, 'node_modules'))) - test('prerelease version of React', async () => { - const output = await getBuildOutput(appDir) + test('prerelease version of react in dev', async () => { + const output = await getDevOutput(appDir) expect(output).toMatch(USING_CREATE_ROOT) expect(output).toMatch(UNSUPPORTED_PRERELEASE) }) - test('prerelease version of React', async () => { - const output = await getDevOutput(appDir) + test('prerelease version of react in build', async () => { + const output = await getBuildOutput(appDir) expect(output).toMatch(USING_CREATE_ROOT) expect(output).toMatch(UNSUPPORTED_PRERELEASE) }) }) }) -describe('React 18 Basic', () => { - describe('Basic hydration', () => { - let app - let appPort - beforeAll(async () => { - await fs.remove(join(appDir, '.next')) - await nextBuild(appDir, [appDir], { nodeArgs }) - appPort = await findPort() - app = await nextStart(appDir, appPort, { nodeArgs }) - }) - afterAll(async () => { - await killApp(app) - }) - it('hydrates correctly for normal page', async () => { - const browser = await webdriver(appPort, '/') - expect(await browser.eval('window.didHydrate')).toBe(true) - expect(await browser.elementById('react-dom-version').text()).toMatch( - /18/ - ) - }) +describe('React 18 basics', () => { + const context = {} + beforeAll(async () => { + await fs.remove(join(appDir, '.next')) + await nextBuild(appDir, [appDir], { nodeArgs }) + context.appPort = await findPort() + context.app = await nextStart(appDir, context.appPort, { nodeArgs }) + }) + afterAll(async () => { + await killApp(context.app) }) - describe('Dynamic import', () => { - const context = {} + it('hydrates correctly for normal page', async () => { + const browser = await webdriver(context.appPort, '/') + expect(await browser.eval('window.didHydrate')).toBe(true) + expect(await browser.elementById('react-dom-version').text()).toMatch(/18/) + }) - beforeAll(async () => { - context.appPort = await findPort() - context.server = await launchApp(appDir, context.appPort, { nodeArgs }) - }) - afterAll(async () => { - await killApp(context.server) - }) + it('SSG works for suspense', async () => { + const res1 = await fetchViaHTTP(context.appPort, '/suspense/thrown') + const res2 = await fetchViaHTTP(context.appPort, '/suspense/no-thrown') - dynamic(context, (p, q) => renderViaHTTP(context.appPort, p, q)) + expect(res1.status).toBe(200) + expect(res2.status).toBe(200) + }) +}) + +describe('Dynamic import', () => { + const context = {} + beforeEach(async () => { + context.appPort = await findPort() + context.server = await launchApp(appDir, context.appPort, { nodeArgs }) + }) + afterEach(async () => { + await killApp(context.server) }) + dynamic(context, (p, q) => renderViaHTTP(context.appPort, p, q)) })