-
Notifications
You must be signed in to change notification settings - Fork 27k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
334 additions
and
6 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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import { staticGenerationAsyncStorage } from '../../client/components/static-generation-async-storage.external' | ||
import { prerenderAsyncStorage } from '../app-render/prerender-async-storage.external' | ||
import { | ||
postponeWithTracking, | ||
throwToInterruptStaticGeneration, | ||
trackDynamicDataInDynamicRender, | ||
} from '../app-render/dynamic-rendering' | ||
import { StaticGenBailoutError } from '../../client/components/static-generation-bailout' | ||
import { makeHangingPromise } from '../dynamic-rendering-utils' | ||
|
||
/** | ||
* This function allows you to indicate that you require an actual user Request before continuing. | ||
* | ||
* During prerendering it will never resolve and during rendering it resolves immediately. | ||
*/ | ||
export function connection(): Promise<void> { | ||
const staticGenerationStore = staticGenerationAsyncStorage.getStore() | ||
const prerenderStore = prerenderAsyncStorage.getStore() | ||
|
||
if (staticGenerationStore) { | ||
if (staticGenerationStore.forceStatic) { | ||
// When using forceStatic we override all other logic and always just return an empty | ||
// headers object without tracking | ||
return Promise.resolve(undefined) | ||
} | ||
|
||
if (staticGenerationStore.isUnstableCacheCallback) { | ||
throw new Error( | ||
`Route ${staticGenerationStore.route} used "connection" inside a function cached with "unstable_cache(...)". The \`connection()\` function is used to wait indicate the subsequent code must only run when there is an actual Request but caches must be able to be produced before a Request so this function is not allowed in this scope. See more info here: https://nextjs.org/docs/app/api-reference/functions/unstable_cache` | ||
) | ||
} else if (staticGenerationStore.dynamicShouldError) { | ||
throw new StaticGenBailoutError( | ||
`Route ${staticGenerationStore.route} with \`dynamic = "error"\` couldn't be rendered statically because it used \`connection\`. See more info here: https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic#dynamic-rendering` | ||
) | ||
} | ||
|
||
if (prerenderStore) { | ||
// We are in PPR and/or dynamicIO mode and prerendering | ||
|
||
if (prerenderStore.controller || prerenderStore.cacheSignal) { | ||
// We use the controller and cacheSignal as an indication we are in dynamicIO mode. | ||
// When resolving headers for a prerender with dynamic IO we return a forever promise | ||
// along with property access tracked synchronous headers. | ||
|
||
// We don't track dynamic access here because access will be tracked when you access | ||
// one of the properties of the headers object. | ||
return makeHangingPromise() | ||
} else { | ||
// We are prerendering with PPR. We need track dynamic access here eagerly | ||
// to keep continuity with how headers has worked in PPR without dynamicIO. | ||
// TODO consider switching the semantic to throw on property access intead | ||
postponeWithTracking( | ||
staticGenerationStore.route, | ||
'connection', | ||
prerenderStore.dynamicTracking | ||
) | ||
} | ||
} else if (staticGenerationStore.isStaticGeneration) { | ||
// We are in a legacy static generation mode while prerendering | ||
// We treat this function call as a bailout of static generation | ||
throwToInterruptStaticGeneration('connection', staticGenerationStore) | ||
} | ||
// We fall through to the dynamic context below but we still track dynamic access | ||
// because in dev we can still error for things like using headers inside a cache context | ||
trackDynamicDataInDynamicRender(staticGenerationStore) | ||
} | ||
|
||
return Promise.resolve(undefined) | ||
} |
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
16 changes: 16 additions & 0 deletions
16
test/e2e/app-dir/dynamic-data/fixtures/cache-scoped/app/connection/page.js
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,16 @@ | ||
import { connection } from 'next/server' | ||
import { unstable_cache as cache } from 'next/cache' | ||
|
||
const cachedConnection = cache(async () => connection()) | ||
|
||
export default async function Page({ searchParams }) { | ||
await cachedConnection() | ||
return ( | ||
<div> | ||
<section> | ||
This example uses `connection()` inside `unstable_cache` which should | ||
cause the build to fail | ||
</section> | ||
</div> | ||
) | ||
} |
8 changes: 5 additions & 3 deletions
8
test/e2e/app-dir/dynamic-data/fixtures/main/app/force-dynamic/page.js
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
8 changes: 5 additions & 3 deletions
8
test/e2e/app-dir/dynamic-data/fixtures/main/app/force-static/page.js
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: 2 additions & 0 deletions
2
test/e2e/app-dir/dynamic-data/fixtures/main/app/top-level/page.js
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
17 changes: 17 additions & 0 deletions
17
test/e2e/app-dir/dynamic-data/fixtures/require-static/app/connection/page.js
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,17 @@ | ||
import Server, { connection } from 'next/server' | ||
|
||
console.log('Server', Server) | ||
|
||
export const dynamic = 'error' | ||
|
||
export default async function Page({ searchParams }) { | ||
await connection() | ||
return ( | ||
<div> | ||
<section> | ||
This example uses `connection()` but is configured with `dynamic = | ||
'error'` which should cause the page to fail to build | ||
</section> | ||
</div> | ||
) | ||
} |
34 changes: 34 additions & 0 deletions
34
test/e2e/app-dir/dynamic-io/app/connection/static-behavior/boundary/page.tsx
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,34 @@ | ||
import { Suspense } from 'react' | ||
import { connection } from 'next/server' | ||
|
||
import { getSentinelValue } from '../../../getSentinelValue' | ||
/** | ||
* This test case is constructed to demonstrate how using the async form of cookies can lead to a better | ||
* prerender with dynamic IO when PPR is on. There is no difference when PPR is off. When PPR is on the second component | ||
* can finish rendering before the prerender completes and so we can produce a static shell where the Fallback closest | ||
* to Cookies access is read | ||
*/ | ||
export default async function Page() { | ||
return ( | ||
<> | ||
<Suspense fallback="loading..."> | ||
<Component /> | ||
</Suspense> | ||
<ComponentTwo /> | ||
<div id="page">{getSentinelValue()}</div> | ||
</> | ||
) | ||
} | ||
|
||
async function Component() { | ||
await connection() | ||
return ( | ||
<div> | ||
cookie <span id="foo">foo</span> | ||
</div> | ||
) | ||
} | ||
|
||
function ComponentTwo() { | ||
return <p>footer</p> | ||
} |
46 changes: 46 additions & 0 deletions
46
test/e2e/app-dir/dynamic-io/app/connection/static-behavior/pass-deeply/page.tsx
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,46 @@ | ||
import { Suspense } from 'react' | ||
import { connection } from 'next/server' | ||
|
||
import { getSentinelValue } from '../../../getSentinelValue' | ||
|
||
export default async function Page() { | ||
const pendingConnection = connection() | ||
return ( | ||
<section> | ||
<h1>Deep Connection Reader</h1> | ||
<p> | ||
This component was passed the connection promise returned by | ||
`connection()`. It is rendered inside a Suspense boundary. | ||
</p> | ||
<p> | ||
If dynamicIO is turned off the `connection()` call would trigger a | ||
dynamic point at the callsite and the suspense boundary would also be | ||
blocked for over one second | ||
</p> | ||
<Suspense | ||
fallback={ | ||
<> | ||
<p>loading connection...</p> | ||
<div id="fallback">{getSentinelValue()}</div> | ||
</> | ||
} | ||
> | ||
<DeepConnectionReader pendingConnection={pendingConnection} /> | ||
</Suspense> | ||
</section> | ||
) | ||
} | ||
|
||
async function DeepConnectionReader({ | ||
pendingConnection, | ||
}: { | ||
pendingConnection: ReturnType<typeof connection> | ||
}) { | ||
await pendingConnection | ||
return ( | ||
<> | ||
<p>The connection was awaited</p> | ||
<div id="page">{getSentinelValue()}</div> | ||
</> | ||
) | ||
} |
31 changes: 31 additions & 0 deletions
31
test/e2e/app-dir/dynamic-io/app/connection/static-behavior/root/page.tsx
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,31 @@ | ||
import { connection } from 'next/server' | ||
|
||
import { getSentinelValue } from '../../../getSentinelValue' | ||
/** | ||
* This test case is constructed to demonstrate how using the async form of cookies can lead to a better | ||
* prerender with dynamic IO when PPR is on. There is no difference when PPR is off. When PPR is on the second component | ||
* can finish rendering before the prerender completes and so we can produce a static shell where the Fallback closest | ||
* to Cookies access is read | ||
*/ | ||
export default async function Page() { | ||
return ( | ||
<> | ||
<Component /> | ||
<ComponentTwo /> | ||
<div id="page">{getSentinelValue()}</div> | ||
</> | ||
) | ||
} | ||
|
||
async function Component() { | ||
await connection() | ||
return ( | ||
<div> | ||
cookie <span id="foo">foo</span> | ||
</div> | ||
) | ||
} | ||
|
||
function ComponentTwo() { | ||
return <p>footer</p> | ||
} |
Oops, something went wrong.