diff --git a/packages/next/server/render.tsx b/packages/next/server/render.tsx index b9ebabfd056ae..88258105aa786 100644 --- a/packages/next/server/render.tsx +++ b/packages/next/server/render.tsx @@ -571,7 +571,11 @@ export async function renderToHTML( } // url will always be set - const routerIsReady = !!(getServerSideProps || hasPageGetInitialProps) + const routerIsReady = !!( + getServerSideProps || + hasPageGetInitialProps || + (!defaultAppGetInitialProps && !isSSG) + ) const router = new ServerRouter( pathname, query, diff --git a/packages/next/shared/lib/router/router.ts b/packages/next/shared/lib/router/router.ts index 8f8bc803e2eb7..ff7692be29198 100644 --- a/packages/next/shared/lib/router/router.ts +++ b/packages/next/shared/lib/router/router.ts @@ -627,6 +627,7 @@ export default class Router implements BaseRouter { this.isReady = !!( self.__NEXT_DATA__.gssp || self.__NEXT_DATA__.gip || + (self.__NEXT_DATA__.appGip && !self.__NEXT_DATA__.gsp) || (!autoExportDynamic && !self.location.search && !process.env.__NEXT_HAS_REWRITES) diff --git a/test/integration/router-is-ready-app-gip/pages/_app.js b/test/integration/router-is-ready-app-gip/pages/_app.js new file mode 100644 index 0000000000000..c49ee290cf782 --- /dev/null +++ b/test/integration/router-is-ready-app-gip/pages/_app.js @@ -0,0 +1,15 @@ +export default function TestApp({ Component, pageProps }) { + return +} + +TestApp.getInitialProps = async ({ Component, ctx }) => { + let pageProps + + if (Component.getInitialProps) { + pageProps = await Component.getInitialProps(ctx) + } + + return { + pageProps, + } +} diff --git a/test/integration/router-is-ready-app-gip/pages/appGip.js b/test/integration/router-is-ready-app-gip/pages/appGip.js new file mode 100644 index 0000000000000..b73c1ad692e95 --- /dev/null +++ b/test/integration/router-is-ready-app-gip/pages/appGip.js @@ -0,0 +1,23 @@ +import { useRouter } from 'next/router' +import { useLayoutEffect } from 'react' + +export default function Page(props) { + const router = useRouter() + + if (typeof window !== 'undefined') { + // eslint-disable-next-line react-hooks/rules-of-hooks + useLayoutEffect(() => { + if (!window.isReadyValues) { + window.isReadyValues = [] + } + window.isReadyValues.push(router.isReady) + }, [router]) + } + + return ( + <> +

appGip page

+

{JSON.stringify(props)}

+ + ) +} diff --git a/test/integration/router-is-ready-app-gip/pages/gsp.js b/test/integration/router-is-ready-app-gip/pages/gsp.js new file mode 100644 index 0000000000000..f9c637c9fecd6 --- /dev/null +++ b/test/integration/router-is-ready-app-gip/pages/gsp.js @@ -0,0 +1,32 @@ +import { useRouter } from 'next/router' +import { useLayoutEffect } from 'react' + +export default function Page(props) { + const router = useRouter() + + if (typeof window !== 'undefined') { + // eslint-disable-next-line react-hooks/rules-of-hooks + useLayoutEffect(() => { + if (!window.isReadyValues) { + window.isReadyValues = [] + } + window.isReadyValues.push(router.isReady) + }, [router]) + } + + return ( + <> +

gsp page

+

{JSON.stringify(props)}

+ + ) +} + +export const getStaticProps = () => { + return { + props: { + hello: 'world', + random: Math.random(), + }, + } +} diff --git a/test/integration/router-is-ready-app-gip/pages/invalid.js b/test/integration/router-is-ready-app-gip/pages/invalid.js new file mode 100644 index 0000000000000..d5c2d696cdd2c --- /dev/null +++ b/test/integration/router-is-ready-app-gip/pages/invalid.js @@ -0,0 +1,15 @@ +import { useRouter } from 'next/router' + +export default function Page(props) { + // eslint-disable-next-line + const router = useRouter() + + // console.log(router.isReady) + + return ( + <> +

invalid page

+

{JSON.stringify(props)}

+ + ) +} diff --git a/test/integration/router-is-ready-app-gip/test/index.test.js b/test/integration/router-is-ready-app-gip/test/index.test.js new file mode 100644 index 0000000000000..ebcb4e689e0d4 --- /dev/null +++ b/test/integration/router-is-ready-app-gip/test/index.test.js @@ -0,0 +1,68 @@ +/* eslint-env jest */ + +import { join } from 'path' +import webdriver from 'next-webdriver' +import { + findPort, + launchApp, + killApp, + nextStart, + nextBuild, + File, +} from 'next-test-utils' + +jest.setTimeout(1000 * 60 * 1) + +let app +let appPort +const appDir = join(__dirname, '../') +const invalidPage = new File(join(appDir, 'pages/invalid.js')) + +function runTests(isDev) { + it('isReady should be true immediately for pages without getStaticProps', async () => { + const browser = await webdriver(appPort, '/appGip') + expect(await browser.eval('window.isReadyValues')).toEqual([true]) + }) + + it('isReady should be true immediately for pages without getStaticProps, with query', async () => { + const browser = await webdriver(appPort, '/appGip?hello=world') + expect(await browser.eval('window.isReadyValues')).toEqual([true]) + }) + + it('isReady should be true immediately for getStaticProps page without query', async () => { + const browser = await webdriver(appPort, '/gsp') + expect(await browser.eval('window.isReadyValues')).toEqual([true]) + }) + + it('isReady should be true after query update for getStaticProps page with query', async () => { + const browser = await webdriver(appPort, '/gsp?hello=world') + expect(await browser.eval('window.isReadyValues')).toEqual([false, true]) + }) +} + +describe('router.isReady with appGip', () => { + describe('dev mode', () => { + beforeAll(async () => { + appPort = await findPort() + app = await launchApp(appDir, appPort) + }) + afterAll(async () => { + await killApp(app) + invalidPage.restore() + }) + + runTests(true) + }) + + describe('production mode', () => { + beforeAll(async () => { + await nextBuild(appDir) + + appPort = await findPort() + app = await nextStart(appDir, appPort) + }) + afterAll(() => killApp(app)) + + runTests() + }) +})