This PR significantly improves performance when navigating through the admin panel by reducing the number of times `initReq` is called. Previously, `initReq`—which handles expensive tasks like initializing Payload and running access control—was called **three times** for a single page load (for the root layout, the root page, and the notFound page).
We initially tried to use React Cache to ensure `initReq` only ran once per request. However, because React Cache performs a shallow object reference check on function arguments, the configuration object we passed (`configPromise`) and the `overrides` object never maintained the same reference, causing the cache to miss.
### What’s Changed
* **New `getInitReqContainer` Helper**
We introduced a helper that provides a stable object reference throughout the entire request. This allows React to properly cache the output, ensuring `initReq` doesn’t get triggered multiple times by mistake.
* **Splitting `initReq` into Two Functions**
The `initReq` logic was split into:
* **`initPartialReq`:** Runs only **once** per request, handling tasks that do not depend on page-level data (e.g., calling `.auth`, which performs a DB request).
* **`initReq`:** Runs **twice** (once for Layout+NotFound page and once for main page), handling tasks, most notably access control, that rely on page-level data such as locale or query parameters. The NotFound page will share the same req as the layout page, as it's not localized, and its access control wouldn't need to access page query / url / locale, just like the layout.
* **Remove duplicative logic**
* Previously, a lot of logic was run in **both** `initReq` **and** the respective page / layout. This was completely unnecessary, as `initReq` was already running that logic. This PR returns the calculated variables from `initReq`, so they don't have to be duplicatively calculated again.
### Performance Gains
* Previously:
* `.auth` call ran **3 times**
* Access control ran **3 times**
* Now:
* `.auth` call runs **1 time**
* Access control runs **2 times**
This change yields a noticeable performance improvement by cutting down on redundant work.
58 lines
1.7 KiB
TypeScript
58 lines
1.7 KiB
TypeScript
import { cache } from 'react'
|
|
|
|
type CachedValue = object
|
|
|
|
// Module-scoped cache container that holds all cached, stable containers
|
|
// - these may hold the stable value, or a promise to the stable value
|
|
const globalCacheContainer: Record<
|
|
string,
|
|
<TValue extends object = CachedValue>(
|
|
...args: unknown[]
|
|
) => {
|
|
value: null | Promise<TValue> | TValue
|
|
}
|
|
> = {}
|
|
|
|
/**
|
|
* Creates a selective cache function that provides more control over React's request-level caching behavior.
|
|
*
|
|
* @param namespace - A namespace to group related cached values
|
|
* @returns A function that manages cached values within the specified namespace
|
|
*/
|
|
export function selectiveCache<TValue extends object = CachedValue>(namespace: string) {
|
|
// Create a stable namespace container if it doesn't exist
|
|
if (!globalCacheContainer[namespace]) {
|
|
globalCacheContainer[namespace] = cache((...args) => ({
|
|
value: null,
|
|
}))
|
|
}
|
|
|
|
/**
|
|
* Gets or creates a cached value for a specific key within the namespace
|
|
*
|
|
* @param key - The key to identify the cached value
|
|
* @param factory - A function that produces the value if not cached
|
|
* @returns The cached or newly created value
|
|
*/
|
|
const getCached = async (factory: () => Promise<TValue>, ...cacheArgs): Promise<TValue> => {
|
|
const stableObjectFn = globalCacheContainer[namespace]
|
|
const stableObject = stableObjectFn<TValue>(...cacheArgs)
|
|
|
|
if (
|
|
stableObject?.value &&
|
|
'then' in stableObject.value &&
|
|
typeof stableObject.value?.then === 'function'
|
|
) {
|
|
return await stableObject.value
|
|
}
|
|
|
|
stableObject.value = factory()
|
|
|
|
return await stableObject.value
|
|
}
|
|
|
|
return {
|
|
get: getCached,
|
|
}
|
|
}
|