perf: optimizes admin ui loading by using react cache to memoize req / auth (#8315)
Uses React `cache` to memoize a lot of the work that the Payload Admin
UI had to perform in parallel, in multiple places.
Specifically, we were running `auth` in three places:
1. `not-found.tsx` - for some reason this renders even if not used
2. `initPage.ts`
3. `RootLayout`
Now, a lot of expensive calculations only happen once and are memoized
per-request. 🎉
This commit is contained in:
@@ -1,17 +1,18 @@
|
||||
import type { AcceptedLanguages, I18nClient } from '@payloadcms/translations'
|
||||
import type { ImportMap, PayloadRequest, SanitizedConfig } from 'payload'
|
||||
import type { AcceptedLanguages } from '@payloadcms/translations'
|
||||
import type { ImportMap, SanitizedConfig } from 'payload'
|
||||
|
||||
import { initI18n, rtlLanguages } from '@payloadcms/translations'
|
||||
import { rtlLanguages } from '@payloadcms/translations'
|
||||
import { RootProvider } from '@payloadcms/ui'
|
||||
import '@payloadcms/ui/scss/app.scss'
|
||||
import { createClientConfig } from '@payloadcms/ui/utilities/createClientConfig'
|
||||
import { headers as getHeaders, cookies as nextCookies } from 'next/headers.js'
|
||||
import { createLocalReq, parseCookies } from 'payload'
|
||||
import { parseCookies } from 'payload'
|
||||
import React from 'react'
|
||||
|
||||
import { getPayloadHMR } from '../../utilities/getPayloadHMR.js'
|
||||
import { getRequestLanguage } from '../../utilities/getRequestLanguage.js'
|
||||
import { getRequestTheme } from '../../utilities/getRequestTheme.js'
|
||||
import { initReq } from '../../utilities/initReq.js'
|
||||
import { DefaultEditView } from '../../views/Edit/Default/index.js'
|
||||
import { DefaultListView } from '../../views/List/Default/index.js'
|
||||
|
||||
@@ -48,25 +49,7 @@ export const RootLayout = async ({
|
||||
|
||||
const payload = await getPayloadHMR({ config })
|
||||
|
||||
const i18n: I18nClient = await initI18n({
|
||||
config: config.i18n,
|
||||
context: 'client',
|
||||
language: languageCode,
|
||||
})
|
||||
|
||||
const req = await createLocalReq(
|
||||
{
|
||||
fallbackLocale: null,
|
||||
req: {
|
||||
headers,
|
||||
host: headers.get('host'),
|
||||
i18n,
|
||||
url: `${payload.config.serverURL}`,
|
||||
} as PayloadRequest,
|
||||
},
|
||||
payload,
|
||||
)
|
||||
const { permissions, user } = await payload.auth({ headers, req })
|
||||
const { i18n, permissions, req, user } = await initReq(config)
|
||||
|
||||
const { clientConfig, render } = await createClientConfig({
|
||||
children,
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import type { I18n } from '@payloadcms/translations'
|
||||
import type { InitPageResult, Locale, PayloadRequest, VisibleEntities } from 'payload'
|
||||
|
||||
import { initI18n } from '@payloadcms/translations'
|
||||
import { findLocaleFromCode } from '@payloadcms/ui/shared'
|
||||
import { headers as getHeaders } from 'next/headers.js'
|
||||
import { createLocalReq, isEntityHidden, parseCookies } from 'payload'
|
||||
@@ -10,7 +8,7 @@ import * as qs from 'qs-esm'
|
||||
import type { Args } from './types.js'
|
||||
|
||||
import { getPayloadHMR } from '../getPayloadHMR.js'
|
||||
import { getRequestLanguage } from '../getRequestLanguage.js'
|
||||
import { initReq } from '../initReq.js'
|
||||
import { handleAdminPage } from './handleAdminPage.js'
|
||||
import { handleAuthRedirect } from './handleAuthRedirect.js'
|
||||
|
||||
@@ -23,39 +21,24 @@ export const initPage = async ({
|
||||
}: Args): Promise<InitPageResult> => {
|
||||
const headers = getHeaders()
|
||||
const payload = await getPayloadHMR({ config: configPromise, importMap })
|
||||
const queryString = `${qs.stringify(searchParams ?? {}, { addQueryPrefix: true })}`
|
||||
|
||||
const {
|
||||
collections,
|
||||
globals,
|
||||
i18n: i18nConfig,
|
||||
localization,
|
||||
routes: { admin: adminRoute },
|
||||
} = payload.config
|
||||
|
||||
const queryString = `${qs.stringify(searchParams ?? {}, { addQueryPrefix: true })}`
|
||||
const cookies = parseCookies(headers)
|
||||
const language = getRequestLanguage({ config: payload.config, cookies, headers })
|
||||
|
||||
const i18n: I18n = await initI18n({
|
||||
config: i18nConfig,
|
||||
context: 'client',
|
||||
language,
|
||||
})
|
||||
const { i18n, permissions, user } = await initReq(payload.config)
|
||||
|
||||
const languageOptions = Object.entries(payload.config.i18n.supportedLanguages || {}).reduce(
|
||||
(acc, [language, languageConfig]) => {
|
||||
if (Object.keys(payload.config.i18n.supportedLanguages).includes(language)) {
|
||||
acc.push({
|
||||
label: languageConfig.translations.general.thisLanguage,
|
||||
value: language,
|
||||
})
|
||||
}
|
||||
|
||||
return acc
|
||||
},
|
||||
[],
|
||||
)
|
||||
// Ideally, we should not need to recreate the req, because
|
||||
// we can get it from the above initReq.
|
||||
|
||||
// We just need to -overwrite- the url and query of the req
|
||||
// we get above. Clone the req? We'll look into that eventually.
|
||||
const req = await createLocalReq(
|
||||
{
|
||||
fallbackLocale: null,
|
||||
@@ -73,7 +56,20 @@ export const initPage = async ({
|
||||
payload,
|
||||
)
|
||||
|
||||
const { permissions, user } = await payload.auth({ headers, req })
|
||||
const languageOptions = Object.entries(payload.config.i18n.supportedLanguages || {}).reduce(
|
||||
(acc, [language, languageConfig]) => {
|
||||
if (Object.keys(payload.config.i18n.supportedLanguages).includes(language)) {
|
||||
acc.push({
|
||||
label: languageConfig.translations.general.thisLanguage,
|
||||
value: language,
|
||||
})
|
||||
}
|
||||
|
||||
return acc
|
||||
},
|
||||
[],
|
||||
)
|
||||
|
||||
req.user = user
|
||||
|
||||
const localeParam = searchParams?.locale as string
|
||||
|
||||
58
packages/next/src/utilities/initReq.ts
Normal file
58
packages/next/src/utilities/initReq.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import type { I18nClient } from '@payloadcms/translations'
|
||||
import type { PayloadRequest, Permissions, SanitizedConfig, User } from 'payload'
|
||||
|
||||
import { initI18n } from '@payloadcms/translations'
|
||||
import { headers as getHeaders } from 'next/headers.js'
|
||||
import { createLocalReq, parseCookies } from 'payload'
|
||||
import { cache } from 'react'
|
||||
|
||||
import { getPayloadHMR } from './getPayloadHMR.js'
|
||||
import { getRequestLanguage } from './getRequestLanguage.js'
|
||||
|
||||
type Result = {
|
||||
i18n: I18nClient
|
||||
permissions: Permissions
|
||||
req: PayloadRequest
|
||||
user: User
|
||||
}
|
||||
|
||||
export const initReq = cache(async function (config: SanitizedConfig): Promise<Result> {
|
||||
const payload = await getPayloadHMR({ config })
|
||||
|
||||
const headers = getHeaders()
|
||||
const cookies = parseCookies(headers)
|
||||
|
||||
const languageCode = getRequestLanguage({
|
||||
config,
|
||||
cookies,
|
||||
headers,
|
||||
})
|
||||
|
||||
const i18n: I18nClient = await initI18n({
|
||||
config: config.i18n,
|
||||
context: 'client',
|
||||
language: languageCode,
|
||||
})
|
||||
|
||||
const req = await createLocalReq(
|
||||
{
|
||||
fallbackLocale: 'null',
|
||||
req: {
|
||||
headers,
|
||||
host: headers.get('host'),
|
||||
i18n,
|
||||
url: `${payload.config.serverURL}`,
|
||||
} as PayloadRequest,
|
||||
},
|
||||
payload,
|
||||
)
|
||||
|
||||
const { permissions, user } = await payload.auth({ headers, req })
|
||||
|
||||
return {
|
||||
i18n,
|
||||
permissions,
|
||||
req,
|
||||
user,
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user