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.
46 lines
1.7 KiB
TypeScript
46 lines
1.7 KiB
TypeScript
import type { ServerFunction, ServerFunctionHandler } from 'payload'
|
|
|
|
import { copyDataFromLocaleHandler } from '@payloadcms/ui/rsc'
|
|
import { buildFormStateHandler } from '@payloadcms/ui/utilities/buildFormState'
|
|
import { buildTableStateHandler } from '@payloadcms/ui/utilities/buildTableState'
|
|
import { schedulePublishHandler } from '@payloadcms/ui/utilities/schedulePublishHandler'
|
|
|
|
import { renderDocumentHandler } from '../views/Document/handleServerFunction.js'
|
|
import { renderDocumentSlotsHandler } from '../views/Document/renderDocumentSlots.js'
|
|
import { renderListHandler } from '../views/List/handleServerFunction.js'
|
|
import { initReq } from './initReq.js'
|
|
|
|
export const handleServerFunctions: ServerFunctionHandler = async (args) => {
|
|
const { name: fnKey, args: fnArgs, config: configPromise, importMap } = args
|
|
|
|
const { req } = await initReq({
|
|
configPromise,
|
|
importMap,
|
|
key: 'RootLayout',
|
|
})
|
|
|
|
const augmentedArgs: Parameters<ServerFunction>[0] = {
|
|
...fnArgs,
|
|
importMap,
|
|
req,
|
|
}
|
|
|
|
const serverFunctions = {
|
|
'copy-data-from-locale': copyDataFromLocaleHandler as any as ServerFunction,
|
|
'form-state': buildFormStateHandler as any as ServerFunction,
|
|
'render-document': renderDocumentHandler as any as ServerFunction,
|
|
'render-document-slots': renderDocumentSlotsHandler as any as ServerFunction,
|
|
'render-list': renderListHandler as any as ServerFunction,
|
|
'schedule-publish': schedulePublishHandler as any as ServerFunction,
|
|
'table-state': buildTableStateHandler as any as ServerFunction,
|
|
}
|
|
|
|
const fn = serverFunctions[fnKey]
|
|
|
|
if (!fn) {
|
|
throw new Error(`Unknown Server Function: ${fnKey}`)
|
|
}
|
|
|
|
return fn(augmentedArgs)
|
|
}
|