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

Ensure static generation storage is accessed correctly #64088

Merged
merged 1 commit into from
Apr 5, 2024
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
16 changes: 6 additions & 10 deletions packages/next/src/client/components/error-boundary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import React from 'react'
import { usePathname } from './navigation'
import { isNextRouterError } from './is-next-router-error'
import { staticGenerationAsyncStorage } from './static-generation-async-storage.external'

const styles = {
error: {
Expand Down Expand Up @@ -50,17 +51,12 @@ interface ErrorBoundaryHandlerState {
// function crashes so we can maintain our previous cache
// instead of caching the error page
function HandleISRError({ error }: { error: any }) {
if (typeof (fetch as any).__nextGetStaticStore === 'function') {
const store:
| undefined
| import('./static-generation-async-storage.external').StaticGenerationStore =
(fetch as any).__nextGetStaticStore()?.getStore()

if (store?.isRevalidate || store?.isStaticGeneration) {
console.error(error)
throw error
}
const store = staticGenerationAsyncStorage.getStore()
if (store?.isRevalidate || store?.isStaticGeneration) {
console.error(error)
throw error
}

return null
}

Expand Down
93 changes: 58 additions & 35 deletions packages/next/src/server/lib/patch-fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,20 @@ import type { FetchMetric } from '../base-http'

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

type Fetcher = typeof fetch

type PatchedFetcher = Fetcher & {
readonly __nextPatched: true
readonly __nextGetStaticStore: () => StaticGenerationAsyncStorage
readonly _nextOriginalFetch: Fetcher
}

function isPatchedFetch(
fetch: Fetcher | PatchedFetcher
): fetch is PatchedFetcher {
return '__nextPatched' in fetch && fetch.__nextPatched === true
}

export function validateRevalidate(
revalidateVal: unknown,
pathname: string
Expand Down Expand Up @@ -174,22 +188,16 @@ interface PatchableModule {
staticGenerationAsyncStorage: StaticGenerationAsyncStorage
}

// we patch fetch to collect cache information used for
// determining if a page is static or not
export function patchFetch({
serverHooks,
staticGenerationAsyncStorage,
}: PatchableModule) {
if (!(globalThis as any)._nextOriginalFetch) {
;(globalThis as any)._nextOriginalFetch = globalThis.fetch
}

if ((globalThis.fetch as any).__nextPatched) return

const { DynamicServerError } = serverHooks
const originFetch: typeof fetch = (globalThis as any)._nextOriginalFetch

globalThis.fetch = async (
function createPatchedFetcher(
originFetch: Fetcher,
{
serverHooks: { DynamicServerError },
staticGenerationAsyncStorage,
}: PatchableModule
): PatchedFetcher {
// Create the patched fetch function. We don't set the type here, as it's
// verified as the return value of this function.
const patched = async (
input: RequestInfo | URL,
init: RequestInit | undefined
) => {
Expand All @@ -211,7 +219,7 @@ export function patchFetch({
const isInternal = (init?.next as any)?.internal === true
const hideSpan = process.env.NEXT_OTEL_FETCH_DISABLED === '1'

return await getTracer().trace(
return getTracer().trace(
isInternal ? NextNodeServerSpan.internalFetch : AppRenderSpan.fetch,
{
hideSpan,
Expand All @@ -225,9 +233,18 @@ export function patchFetch({
},
},
async () => {
const staticGenerationStore: StaticGenerationStore =
staticGenerationAsyncStorage.getStore() ||
(fetch as any).__nextGetStaticStore?.()
// If this is an internal fetch, we should not do any special treatment.
if (isInternal) return originFetch(input, init)

const staticGenerationStore = staticGenerationAsyncStorage.getStore()

// If the staticGenerationStore is not available, we can't do any
// special treatment of fetch, therefore fallback to the original
// fetch implementation.
if (!staticGenerationStore || staticGenerationStore.isDraftMode) {
return originFetch(input, init)
}

const isRequestInput =
input &&
typeof input === 'object' &&
Expand All @@ -239,17 +256,6 @@ export function patchFetch({
return value || (isRequestInput ? (input as any)[field] : null)
}

// If the staticGenerationStore is not available, we can't do any
// special treatment of fetch, therefore fallback to the original
// fetch implementation.
if (
!staticGenerationStore ||
isInternal ||
staticGenerationStore.isDraftMode
) {
return originFetch(input, init)
}

let revalidate: number | undefined | false = undefined
const getNextField = (field: 'revalidate' | 'tags') => {
return typeof init?.next?.[field] !== 'undefined'
Expand Down Expand Up @@ -718,8 +724,25 @@ export function patchFetch({
}
)
}
;(globalThis.fetch as any).__nextGetStaticStore = () => {
return staticGenerationAsyncStorage
}
;(globalThis.fetch as any).__nextPatched = true

// Attach the necessary properties to the patched fetch function.
patched.__nextPatched = true as const
patched.__nextGetStaticStore = () => staticGenerationAsyncStorage
patched._nextOriginalFetch = originFetch

return patched
}

// we patch fetch to collect cache information used for
// determining if a page is static or not
export function patchFetch(options: PatchableModule) {
// If we've already patched fetch, we should not patch it again.
if (isPatchedFetch(globalThis.fetch)) return

// Grab the original fetch function. We'll attach this so we can use it in
// the patched fetch function.
const original = globalThis.fetch

// Set the global fetch to the patched fetch.
globalThis.fetch = createPatchedFetcher(original, options)
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { RequestCookies } from '../cookies'
import type { StaticGenerationStore } from '../../../../client/components/static-generation-async-storage.external'

import { ResponseCookies } from '../cookies'
import { ReflectAdapter } from './reflect'
import { staticGenerationAsyncStorage } from '../../../../client/components/static-generation-async-storage.external'

/**
* @internal
Expand Down Expand Up @@ -106,9 +106,7 @@ export class MutableRequestCookiesAdapter {
const modifiedCookies = new Set<string>()
const updateResponseCookies = () => {
// TODO-APP: change method of getting staticGenerationAsyncStore
const staticGenerationAsyncStore = (fetch as any)
.__nextGetStaticStore?.()
?.getStore() as undefined | StaticGenerationStore
const staticGenerationAsyncStore = staticGenerationAsyncStorage.getStore()
if (staticGenerationAsyncStore) {
staticGenerationAsyncStore.pathWasRevalidated = true
}
Expand Down
13 changes: 2 additions & 11 deletions packages/next/src/server/web/spec-extension/revalidate.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import type {
StaticGenerationAsyncStorage,
StaticGenerationStore,
} from '../../../client/components/static-generation-async-storage.external'
import { trackDynamicDataAccessed } from '../../app-render/dynamic-rendering'
import { isDynamicRoute } from '../../../shared/lib/router/utils'
import {
NEXT_CACHE_IMPLICIT_TAG_ID,
NEXT_CACHE_SOFT_TAG_MAX_LENGTH,
} from '../../../lib/constants'
import { getPathname } from '../../../lib/url'
import { staticGenerationAsyncStorage } from '../../../client/components/static-generation-async-storage.external'

/**
* This function allows you to purge [cached data](https://nextjs.org/docs/app/building-your-application/caching) on-demand for a specific cache tag.
Expand Down Expand Up @@ -45,13 +42,7 @@ export function revalidatePath(originalPath: string, type?: 'layout' | 'page') {
}

function revalidate(tag: string, expression: string) {
const staticGenerationAsyncStorage = (
fetch as any
).__nextGetStaticStore?.() as undefined | StaticGenerationAsyncStorage

const store: undefined | StaticGenerationStore =
staticGenerationAsyncStorage?.getStore()

const store = staticGenerationAsyncStorage.getStore()
if (!store || !store.incrementalCache) {
throw new Error(
`Invariant: static generation store missing in ${expression}`
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import type { StaticGenerationAsyncStorage } from '../../../client/components/static-generation-async-storage.external'
import type { IncrementalCache } from '../../lib/incremental-cache'

import { staticGenerationAsyncStorage as _staticGenerationAsyncStorage } from '../../../client/components/static-generation-async-storage.external'
import { CACHE_ONE_YEAR } from '../../../lib/constants'
import {
addImplicitTags,
validateRevalidate,
validateTags,
} from '../../lib/patch-fetch'
import { staticGenerationAsyncStorage } from '../../../client/components/static-generation-async-storage.external'

type Callback = (...args: any[]) => Promise<any>

Expand Down Expand Up @@ -59,11 +58,6 @@ export function unstable_cache<T extends Callback>(
tags?: string[]
} = {}
): T {
const staticGenerationAsyncStorage =
((fetch as any).__nextGetStaticStore?.() as
| StaticGenerationAsyncStorage
| undefined) ?? _staticGenerationAsyncStorage

if (options.revalidate === 0) {
throw new Error(
`Invariant revalidate: 0 can not be passed to unstable_cache(), must be "false" or "> 0" ${cb.toString()}`
Expand Down