Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PPR Fetch Fix #57327

Merged
merged 2 commits into from
Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions packages/next/src/client/components/maybe-postpone.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { StaticGenerationStore } from './static-generation-async-storage.external'

export function maybePostpone(
staticGenerationStore: StaticGenerationStore,
reason: string
) {
// If we aren't performing a static generation or we aren't using PPR then
// we don't need to postpone.
if (
!staticGenerationStore.isStaticGeneration ||
!staticGenerationStore.experimental.ppr
) {
return
}

// App Route's cannot be postponed, so we only postpone if it's a page. If the
// postpone API is available, use it now.
const React = require('react') as typeof import('react')
if (typeof React.unstable_postpone !== 'function') return

React.unstable_postpone(reason)
}
46 changes: 20 additions & 26 deletions packages/next/src/client/components/static-generation-bailout.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { DynamicServerError } from './hooks-server-context'
import { maybePostpone } from './maybe-postpone'
import { staticGenerationAsyncStorage } from './static-generation-async-storage.external'

class StaticGenBailoutError extends Error {
Expand All @@ -25,45 +26,38 @@ export const staticGenerationBailout: StaticGenerationBailout = (
opts
) => {
const staticGenerationStore = staticGenerationAsyncStorage.getStore()
if (!staticGenerationStore) return false

if (staticGenerationStore?.forceStatic) {
if (staticGenerationStore.forceStatic) {
return true
}

if (staticGenerationStore?.dynamicShouldError) {
if (staticGenerationStore.dynamicShouldError) {
throw new StaticGenBailoutError(
formatErrorMessage(reason, { ...opts, dynamic: opts?.dynamic ?? 'error' })
)
}

if (staticGenerationStore && !staticGenerationStore?.experimental.ppr) {
staticGenerationStore.revalidate = 0
const message = formatErrorMessage(reason, {
...opts,
// this error should be caught by Next to bail out of static generation
// in case it's uncaught, this link provides some additional context as to why
link: 'https://nextjs.org/docs/messages/dynamic-server-error',
})

if (!opts?.dynamic) {
// we can statically prefetch pages that opt into dynamic,
// but not things like headers/cookies
staticGenerationStore.staticPrefetchBailout = true
}
}

if (staticGenerationStore?.isStaticGeneration) {
const message = formatErrorMessage(reason, {
...opts,
// this error should be caught by Next to bail out of static generation
// in case it's uncaught, this link provides some additional context as to why
link: 'https://nextjs.org/docs/messages/dynamic-server-error',
})
maybePostpone(staticGenerationStore, message)

if (staticGenerationStore?.experimental.ppr) {
const React = require('react') as typeof import('react')
// As this is a bailout, we don't want to revalidate, so set the revalidate
// to 0.
staticGenerationStore.revalidate = 0

// App Route's cannot be postponed, so we only postpone if it's a page.
if (typeof React.unstable_postpone === 'function') {
// This throws a postpone error similar to the below error.
React.unstable_postpone(message)
}
}
if (!opts?.dynamic) {
// we can statically prefetch pages that opt into dynamic,
// but not things like headers/cookies
staticGenerationStore.staticPrefetchBailout = true
}

if (staticGenerationStore.isStaticGeneration) {
const err = new DynamicServerError(message)
staticGenerationStore.dynamicUsageDescription = reason
staticGenerationStore.dynamicUsageStack = err.stack
Expand Down
116 changes: 66 additions & 50 deletions packages/next/src/server/lib/patch-fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
NEXT_CACHE_TAG_MAX_LENGTH,
} from '../../lib/constants'
import * as Log from '../../build/output/log'
import { maybePostpone } from '../../client/components/maybe-postpone'

const isEdgeRuntime = process.env.NEXT_RUNTIME === 'edge'

Expand Down Expand Up @@ -70,13 +71,8 @@ const getDerivedTags = (pathname: string): string[] => {
return derivedTags
}

export function addImplicitTags(
staticGenerationStore: ReturnType<StaticGenerationAsyncStorage['getStore']>
) {
export function addImplicitTags(staticGenerationStore: StaticGenerationStore) {
const newTags: string[] = []
if (!staticGenerationStore) {
return newTags
}
const { pagePath, urlPathname } = staticGenerationStore

if (!Array.isArray(staticGenerationStore.tags)) {
Expand Down Expand Up @@ -106,7 +102,7 @@ export function addImplicitTags(
}

function trackFetchMetric(
staticGenerationStore: ReturnType<StaticGenerationAsyncStorage['getStore']>,
staticGenerationStore: StaticGenerationStore,
ctx: {
url: string
status: number
Expand Down Expand Up @@ -276,11 +272,19 @@ export function patchFetch({

if (_cache === 'force-cache') {
curRevalidate = false
}
if (['no-cache', 'no-store'].includes(_cache || '')) {
} else if (
_cache === 'no-cache' ||
_cache === 'no-store' ||
isForceNoStore ||
isOnlyNoStore
) {
curRevalidate = 0
}

if (_cache === 'no-cache' || _cache === 'no-store') {
cacheReason = `cache: ${_cache}`
}

if (typeof curRevalidate === 'number' || curRevalidate === false) {
revalidate = curRevalidate
}
Expand All @@ -306,7 +310,6 @@ export function patchFetch({
staticGenerationStore.revalidate === 0

if (isForceNoStore) {
revalidate = 0
cacheReason = 'fetchCache = force-no-store'
}

Expand All @@ -320,7 +323,6 @@ export function patchFetch({
`cache: 'force-cache' used on fetch for ${fetchUrl} with 'export const fetchCache = 'only-no-store'`
)
}
revalidate = 0
cacheReason = 'fetchCache = only-no-store'
}

Expand Down Expand Up @@ -370,6 +372,11 @@ export function patchFetch({
(typeof staticGenerationStore.revalidate === 'number' &&
revalidate < staticGenerationStore.revalidate))))
) {
// If enabled, we should bail out of static generation.
if (revalidate === 0) {
maybePostpone(staticGenerationStore, 'revalidate: 0')
}

staticGenerationStore.revalidate = revalidate
}

Expand Down Expand Up @@ -566,16 +573,47 @@ export function patchFetch({
}
}

if (staticGenerationStore.isStaticGeneration) {
if (init && typeof init === 'object') {
const cache = init.cache
// Delete `cache` property as Cloudflare Workers will throw an error
if (isEdgeRuntime) {
delete init.cache
}
if (cache === 'no-store') {
staticGenerationStore.revalidate = 0
const dynamicUsageReason = `no-store fetch ${input}${
if (
staticGenerationStore.isStaticGeneration &&
init &&
typeof init === 'object'
) {
const { cache } = init

// Delete `cache` property as Cloudflare Workers will throw an error
if (isEdgeRuntime) delete init.cache

if (cache === 'no-store') {
const dynamicUsageReason = `no-store fetch ${input}${
staticGenerationStore.urlPathname
? ` ${staticGenerationStore.urlPathname}`
: ''
}`
const err = new DynamicServerError(dynamicUsageReason)
staticGenerationStore.dynamicUsageErr = err
staticGenerationStore.dynamicUsageStack = err.stack
staticGenerationStore.dynamicUsageDescription = dynamicUsageReason

// If enabled, we should bail out of static generation.
maybePostpone(staticGenerationStore, dynamicUsageReason)

// PPR is not enabled, or React postpone is not available, we
// should set the revalidate to 0.
staticGenerationStore.revalidate = 0
}

const hasNextConfig = 'next' in init
const { next = {} } = init
if (
typeof next.revalidate === 'number' &&
(typeof staticGenerationStore.revalidate === 'undefined' ||
(typeof staticGenerationStore.revalidate === 'number' &&
next.revalidate < staticGenerationStore.revalidate))
) {
const forceDynamic = staticGenerationStore.forceDynamic

if (!forceDynamic && next.revalidate === 0) {
const dynamicUsageReason = `revalidate: 0 fetch ${input}${
staticGenerationStore.urlPathname
? ` ${staticGenerationStore.urlPathname}`
: ''
Expand All @@ -584,39 +622,17 @@ export function patchFetch({
staticGenerationStore.dynamicUsageErr = err
staticGenerationStore.dynamicUsageStack = err.stack
staticGenerationStore.dynamicUsageDescription = dynamicUsageReason
}

const hasNextConfig = 'next' in init
const next = init.next || {}
if (
typeof next.revalidate === 'number' &&
(typeof staticGenerationStore.revalidate === 'undefined' ||
(typeof staticGenerationStore.revalidate === 'number' &&
next.revalidate < staticGenerationStore.revalidate))
) {
const forceDynamic = staticGenerationStore.forceDynamic

if (!forceDynamic || next.revalidate !== 0) {
staticGenerationStore.revalidate = next.revalidate
}
// If enabled, we should bail out of static generation.
maybePostpone(staticGenerationStore, dynamicUsageReason)
}

if (!forceDynamic && next.revalidate === 0) {
const dynamicUsageReason = `revalidate: ${
next.revalidate
} fetch ${input}${
staticGenerationStore.urlPathname
? ` ${staticGenerationStore.urlPathname}`
: ''
}`
const err = new DynamicServerError(dynamicUsageReason)
staticGenerationStore.dynamicUsageErr = err
staticGenerationStore.dynamicUsageStack = err.stack
staticGenerationStore.dynamicUsageDescription =
dynamicUsageReason
}
if (!forceDynamic || next.revalidate !== 0) {
staticGenerationStore.revalidate = next.revalidate
}
if (hasNextConfig) delete init.next
}

if (hasNextConfig) delete init.next
}

return doOriginalFetch(false, cacheReasonOverride).finally(handleUnlock)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export function unstable_cache<T extends Callback>(
}
}
}
const implicitTags = addImplicitTags(store)
const implicitTags = store ? addImplicitTags(store) : []

const cacheKey = await incrementalCache?.fetchCacheKey(joinedKey)
const cacheEntry =
Expand Down
Loading