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

Add cache reason for using fetch with noStore #60630

Merged
merged 2 commits into from
Jan 14, 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
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export interface StaticGenerationStore {
fetchMetrics?: FetchMetrics

isDraftMode?: boolean
isUnstableNoStore?: boolean
}

export type StaticGenerationAsyncStorage =
Expand Down
4 changes: 4 additions & 0 deletions packages/next/src/server/lib/patch-fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ export function patchFetch({
staticGenerationStore.fetchCache === 'only-no-store'
const isForceNoStore =
staticGenerationStore.fetchCache === 'force-no-store'
const isUsingNoStore = !!staticGenerationStore.isUnstableNoStore

let _cache = getRequestMeta('cache')
let cacheReason = ''
Expand Down Expand Up @@ -386,6 +387,9 @@ export function patchFetch({
} else if (isDefaultNoStore) {
revalidate = 0
cacheReason = 'fetchCache = default-no-store'
} else if (isUsingNoStore) {
revalidate = 0
cacheReason = 'noStore call'
} else {
cacheReason = 'auto cache'
revalidate =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ export function unstable_noStore() {
return
}

// Mark the static generation context has unstable_noStore
if (staticGenerationStore) {
staticGenerationStore.isUnstableNoStore = true
}
staticGenerationBailout('unstable_noStore', {
link: 'https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic#dynamic-rendering',
})
Expand Down
21 changes: 21 additions & 0 deletions test/e2e/app-dir/logging/app/no-store/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { unstable_noStore } from 'next/cache'

async function getUncachedRandomData() {
unstable_noStore()
const res = await fetch(
'https://next-data-api-endpoint.vercel.app/api/random?another-no-cache'
)
const data = await res.json()
return data
}

export default async function Page() {
const uncachedData = await getUncachedRandomData()
console.log('uncachedData', uncachedData)
return (
<div>
<p>random: {Math.random()}</p>
<p id="uncached-data">uncachedData: {uncachedData}</p>
</div>
)
}
83 changes: 36 additions & 47 deletions test/e2e/app-dir/logging/fetch-logging.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import path from 'path'
import fs from 'fs'
import stripAnsi from 'strip-ansi'
import { check } from 'next-test-utils'
import { retry } from 'next-test-utils'
import { createNextDescribe } from 'e2e-utils'

function parseLogsFromCli(cliOutput: string) {
Expand Down Expand Up @@ -56,26 +56,20 @@ createNextDescribe(
const outputIndex = next.cliOutput.length
await next.fetch('/default-cache')

await check(() => {
await retry(() => {
const logs = stripAnsi(next.cliOutput.slice(outputIndex))
const hasLogs = logs.includes('GET /default-cache 200')

if (isNextDev && hasLogs) {
return 'success'
}

if (!isNextDev && !hasLogs) {
return 'success'
}
}, 'success')
expect(isNextDev ? hasLogs : !hasLogs).toBe(true)
})
})

if (isNextDev) {
it("should log 'skip' cache status with a reason when cache: 'no-cache' is used", async () => {
const outputIndex = next.cliOutput.length
await next.fetch('/default-cache')

await check(() => {
await retry(() => {
const logs = parseLogsFromCli(next.cliOutput.slice(outputIndex))

const logEntry = logs.find((log) =>
Expand All @@ -86,79 +80,78 @@ createNextDescribe(
!withFullUrlFetches
)

if (logEntry?.cache === 'cache: no-cache') {
return 'success'
}
}, 'success')
expect(logEntry?.cache).toBe('cache: no-cache')
})
})

it("should log 'skip' cache status with a reason when revalidate: 0 is used", async () => {
const outputIndex = next.cliOutput.length
await next.fetch('/default-cache')
await check(() => {
await retry(() => {
const logs = parseLogsFromCli(next.cliOutput.slice(outputIndex))

const logEntry = logs.find((log) =>
log.url.includes('api/random?revalidate-0')
)

if (logEntry?.cache === 'revalidate: 0') {
return 'success'
}
}, 'success')
expect(logEntry?.cache).toBe('revalidate: 0')
})
})

it("should log 'skip' cache status with a reason when the browser indicates caching should be ignored", async () => {
const outputIndex = next.cliOutput.length
await next.fetch('/default-cache', {
headers: { 'Cache-Control': 'no-cache' },
})
await check(() => {
await retry(() => {
const logs = parseLogsFromCli(next.cliOutput.slice(outputIndex))

const logEntry = logs.find((log) =>
log.url.includes('api/random?auto-cache')
)

if (
logEntry?.cache === 'cache-control: no-cache (hard refresh)'
) {
return 'success'
}
}, 'success')
expect(logEntry?.cache).toBe(
'cache-control: no-cache (hard refresh)'
)
})
})

it('should log requests with correct indentation', async () => {
const outputIndex = next.cliOutput.length
await next.fetch('/default-cache')

await check(() => {
await retry(() => {
const logs = stripAnsi(next.cliOutput.slice(outputIndex))
const hasLogs =
logs.includes(' GET /default-cache') &&
logs.includes(' │ GET ') &&
logs.includes(' │ │ GET ') &&
logs.includes(' │ │ Cache missed reason')

if (hasLogs) {
return 'success'
}
}, 'success')
expect(hasLogs).toBe(true)
})
})

it('should show cache reason of noStore when use with fetch', async () => {
const logLength = next.cliOutput.length
await next.fetch('/no-store')

await retry(() => {
const output = stripAnsi(next.cliOutput.slice(logLength))
expect(output).toContain('Cache missed reason: (noStore call)')
})
})
}
} else {
// No fetches logging enabled
it('should not log fetch requests at all', async () => {
const outputIndex = next.cliOutput.length
await next.fetch('/default-cache')

await check(() => {
await retry(() => {
const logs = stripAnsi(next.cliOutput.slice(outputIndex))
if (logs.includes('GET /default-cache 200')) {
return 'fail'
}

return 'success'
}, 'success')
expect(logs).not.toContain('GET /default-cache 200')
})
})
}

Expand All @@ -167,28 +160,24 @@ createNextDescribe(
const logLength = next.cliOutput.length
await next.fetch('/')

await check(() => {
await retry(() => {
const output = stripAnsi(next.cliOutput.slice(logLength))
expect(output).toContain('/')
expect(output).not.toContain('/page')

return 'success'
}, /success/)
})
})

it('should not contain metadata internal segments for dynamic metadata routes', async () => {
const logLength = next.cliOutput.length
await next.fetch('/dynamic/big/icon')

await check(() => {
await retry(() => {
const output = stripAnsi(next.cliOutput.slice(logLength))
expect(output).toContain('/dynamic/[slug]/icon')
expect(output).not.toContain('/(group)')
expect(output).not.toContain('[[...__metadata_id__]]')
expect(output).not.toContain('/route')

return 'success'
}, /success/)
})
})
}
}
Expand Down