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:
James Mikrut
2024-09-19 17:58:34 -04:00
committed by GitHub
parent 9c3f863ad2
commit c6a7ad2817
3 changed files with 85 additions and 48 deletions

View File

@@ -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,

View File

@@ -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

View 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,
}
})