diff --git a/packages/next/server/app-render.tsx b/packages/next/server/app-render.tsx index 6e2b8fd2cc10c6f..cda73f9dc01613c 100644 --- a/packages/next/server/app-render.tsx +++ b/packages/next/server/app-render.tsx @@ -39,6 +39,31 @@ import { HeadManagerContext } from '../shared/lib/head-manager-context' import { Writable } from 'stream' import stringHash from 'next/dist/compiled/string-hash' +function preloadComponent(Component: any, props: any) { + const prev = console.error + // Hide invalid hook call warning when calling component + console.error = (msg) => { + if (msg.startsWith('Invalid hook call..')) { + // ignore + } else { + // @ts-expect-error argument is defined + prev.apply(console, arguments) + } + } + try { + let result = Component(props) + return function () { + // We know what this component will render already. + return result + } + } catch (x) { + // something suspended or errored, try again later + } finally { + console.error = prev + } + return Component +} + const INTERNAL_HEADERS_INSTANCE = Symbol('internal for headers readonly') function readonlyHeadersError() { @@ -1027,7 +1052,7 @@ export async function renderToHTMLOrFlight( /** * The React Component to render. */ - const Component = layoutOrPageMod + let Component = layoutOrPageMod ? interopDefault(layoutOrPageMod) : undefined @@ -1181,10 +1206,23 @@ export async function renderToHTMLOrFlight( } } + const props = { + ...parallelRouteComponents, + // TODO-APP: params and query have to be blocked parallel route names. Might have to add a reserved name list. + // Params are always the current params that apply to the layout + // If you have a `/dashboard/[team]/layout.js` it will provide `team` as a param but not anything further down. + params: currentParams, + // Query is only provided to page + ...(isPage ? { searchParams: query } : {}), + } + + // Eagerly execute layout/page component to trigger fetches early. + Component = await Promise.resolve().then(() => { + return preloadComponent(Component, props) + }) + return { Component: () => { - let props = {} - // Add extra cache busting (DEV only) for https://github.com/vercel/next.js/issues/5860 // See also https://bugs.webkit.org/show_bug.cgi?id=187726 const cacheBustingUrlSuffix = dev ? `?ts=${Date.now()}` : '' @@ -1218,16 +1256,7 @@ export async function renderToHTMLOrFlight( /> )) : null} - + {/* {HeadTags ? : null} */} ) diff --git a/test/e2e/app-dir/app-rendering/app/ssr-only/slow/layout.js b/test/e2e/app-dir/app-rendering/app/ssr-only/slow/layout.js index 8d13273acc6af11..bee98f1a4c5386b 100644 --- a/test/e2e/app-dir/app-rendering/app/ssr-only/slow/layout.js +++ b/test/e2e/app-dir/app-rendering/app/ssr-only/slow/layout.js @@ -1,5 +1,7 @@ import { use } from 'react' +let i + async function getData() { await new Promise((resolve) => setTimeout(resolve, 5000)) return { @@ -8,7 +10,11 @@ async function getData() { } export default function gsspLayout(props) { - const data = use(getData()) + // TODO-APP: refactor this test page to `async function` instead. + if (!i) { + i = getData() + } + const data = use(i) return ( <>

{data.message}

diff --git a/test/e2e/app-dir/app-rendering/app/ssr-only/slow/page.js b/test/e2e/app-dir/app-rendering/app/ssr-only/slow/page.js index 71eec5323e43d4e..686d745b88057d8 100644 --- a/test/e2e/app-dir/app-rendering/app/ssr-only/slow/page.js +++ b/test/e2e/app-dir/app-rendering/app/ssr-only/slow/page.js @@ -1,5 +1,6 @@ import { use } from 'react' +let i async function getData() { await new Promise((resolve) => setTimeout(resolve, 5000)) return { @@ -8,7 +9,11 @@ async function getData() { } export default function nestedPage(props) { - const data = use(getData()) + // TODO-APP: refactor this test page to `async function` instead. + if (!i) { + i = getData() + } + const data = use(i) return ( <>

{data.message}

diff --git a/test/e2e/app-dir/rendering.test.ts b/test/e2e/app-dir/rendering.test.ts index 28350408f17e6e5..3b6b0ac0cea837a 100644 --- a/test/e2e/app-dir/rendering.test.ts +++ b/test/e2e/app-dir/rendering.test.ts @@ -37,14 +37,14 @@ describe('app dir rendering', () => { expect($('#page-message').text()).toBe('hello from page') }) - it('should run data in parallel', async () => { - // const startTime = Date.now() + it('should run data fetch in parallel', async () => { + const startTime = Date.now() const html = await renderViaHTTP(next.url, '/ssr-only/slow') - // const endTime = Date.now() - // const duration = endTime - startTime + const endTime = Date.now() + const duration = endTime - startTime // Each part takes 5 seconds so it should be below 10 seconds // Using 7 seconds to ensure external factors causing slight slowness don't fail the tests - // expect(duration < 7000).toBe(true) + expect(duration < 7000).toBe(true) const $ = cheerio.load(html) expect($('#slow-layout-message').text()).toBe('hello from slow layout') expect($('#slow-page-message').text()).toBe('hello from slow page')