Skip to content

Commit

Permalink
fix: postpone if enabled during fetch while no-store or no-cache is c…
Browse files Browse the repository at this point in the history
…onfigured
  • Loading branch information
wyattjoh committed Oct 24, 2023
1 parent 2275bf1 commit 3678c55
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 77 deletions.
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

0 comments on commit 3678c55

Please sign in to comment.