148 lines
5.1 KiB
Markdown
148 lines
5.1 KiB
Markdown
---
|
|
title: "Payload CMS — Performance Optimization"
|
|
aliases: [payloadcms-performance, payload-performance]
|
|
tags: [payloadcms, performance, optimization, nextjs]
|
|
sources: [raw/performance__overview.md]
|
|
created: 2026-05-15
|
|
updated: 2026-05-15
|
|
---
|
|
|
|
# Payload CMS — Performance Optimization
|
|
|
|
Guide to making Payload apps run as fast as possible — database, APIs, Admin Panel, and local dev.
|
|
|
|
## Database
|
|
|
|
### Proximity
|
|
- Host database in the **same region** as the server — cross-region latency is significant
|
|
|
|
### Indexes
|
|
- Add `index: true` to any field queried frequently → avoids full document scans
|
|
- See [[wiki/payloadcms/database-indexes|Database Indexes]] for compound indexes and gotchas
|
|
|
|
### Queries
|
|
- Combine multiple query optimizations together: `select`, `depth`, `limit`, `populate`
|
|
- See [[wiki/payloadcms/queries|Queries]] → Performance section for `depth`/`select` patterns
|
|
|
|
## API Request Lifecycle
|
|
|
|
Payload's request lifecycle includes hooks, access control, and validations — all add overhead.
|
|
|
|
### Hooks
|
|
- Write **lightweight hooks** — avoid blocking operations or unnecessary DB calls
|
|
- Offload long-running tasks to [[wiki/payloadcms/jobs-queue|Jobs Queue]]
|
|
- Prevent memory leaks (no unclosed streams, unbounded arrays)
|
|
- See [[wiki/payloadcms/hooks|Hooks]] → Performance section
|
|
|
|
### Validations
|
|
- Async or heavy validation functions → only run when necessary
|
|
- Gate expensive validators with early-exit conditions
|
|
- See [[wiki/payloadcms/fields-overview|Fields Overview]] → Validation Performance section
|
|
|
|
### Custom Admin Components
|
|
- Apply React best practices: **memoization**, **lazy loading**, avoid unnecessary re-renders
|
|
- Use [`@next/bundle-analyzer`](https://nextjs.org/docs/app/guides/package-bundling) to identify large components
|
|
- See [[wiki/payloadcms/custom-components-authoring|Custom Components — Authoring Guide]] → Performance section
|
|
|
|
## Config-Level Optimizations
|
|
|
|
### Block References
|
|
Reuse a block definition across multiple collections without duplicating config:
|
|
|
|
```ts
|
|
const config = buildConfig({
|
|
blocks: [
|
|
{ slug: 'TextBlock', fields: [{ name: 'text', type: 'text' }] },
|
|
],
|
|
collections: [
|
|
{
|
|
slug: 'posts',
|
|
fields: [{ name: 'content', type: 'blocks', blockReferences: ['TextBlock'], blocks: [] }],
|
|
},
|
|
{
|
|
slug: 'pages',
|
|
fields: [{ name: 'content', type: 'blocks', blockReferences: ['TextBlock'], blocks: [] }],
|
|
},
|
|
],
|
|
})
|
|
```
|
|
|
|
**Why:** fewer fields to traverse for permission processing; less data sent to Admin Panel.
|
|
|
|
### Cached Payload Instance
|
|
Never instantiate Payload more than once — use `getPayload`:
|
|
|
|
```ts
|
|
import { getPayload } from 'payload'
|
|
import config from '@payload-config'
|
|
|
|
const payload = await getPayload({ config })
|
|
```
|
|
|
|
## Direct Database Calls
|
|
|
|
Bypass hooks, access control, and validation for known-safe operations:
|
|
|
|
```ts
|
|
// Single DB round-trip instead of fetch→update→fetch
|
|
await payload.db.updateOne({
|
|
collection: 'posts',
|
|
id: post.id,
|
|
data: { title: 'New Title' },
|
|
returning: false, // skip returning the updated doc — saves one DB call
|
|
})
|
|
```
|
|
|
|
> **Warning:** direct calls skip hooks, validations, and do NOT start a transaction automatically.
|
|
> `returning: false` is only available on `payload.db.*` — not the Local API.
|
|
|
|
## Frontend Bundle
|
|
|
|
### Tree-shake `@payloadcms/ui`
|
|
Only in non-admin code (front-end imports):
|
|
|
|
```ts
|
|
// Bad — bundles entire UI library
|
|
import { Button } from '@payloadcms/ui'
|
|
|
|
// Good — tree-shaken
|
|
import { Button } from '@payloadcms/ui/elements/Button'
|
|
```
|
|
|
|
Admin Panel custom components can still use `import { Button } from '@payloadcms/ui'` safely.
|
|
|
|
## Local Development Speed
|
|
|
|
### Turbopack (Next.js 15)
|
|
```json
|
|
{ "scripts": { "dev": "next dev --turbo" } }
|
|
```
|
|
Next.js 16+ enables Turbopack by default.
|
|
|
|
### Skip Server Bundle During Dev
|
|
Avoids compiling thousands of Payload modules on each dev restart.
|
|
Enabled by default in `create-payload-app` ≥ v3.28.0:
|
|
|
|
```ts
|
|
// next.config.js
|
|
export default withPayload(nextConfig, { devBundleServerPackages: false })
|
|
```
|
|
|
|
## Key Takeaways
|
|
|
|
- **Same-region DB** is the single biggest latency win — do this first
|
|
- **Index frequently queried fields** — `index: true` is cheap to add, big query speedup
|
|
- **Use `payload.db.updateOne` with `returning: false`** when you don't need the result back
|
|
- **Block references** reduce config size and Admin Panel data transfer
|
|
- **`getPayload`** — never call `getPayload({ config })` multiple times; it returns a cached instance
|
|
- **Hooks are the main lifecycle cost** — keep them synchronous where possible, offload heavy work to jobs
|
|
- **Dev performance:** `--turbo` + `devBundleServerPackages: false` dramatically reduces cold start time
|
|
- **Frontend bundle:** always use full import paths for `@payloadcms/ui` outside the Admin Panel
|
|
|
|
## Related
|
|
|
|
- [[wiki/payloadcms/database-indexes|Database Indexes]] — single-field and compound indexes
|
|
- [[wiki/payloadcms/queries|Queries]] — depth, select, populate optimization
|
|
- [[wiki/payloadcms/hooks|Hooks]] — hook performance patterns
|
|
- [[wiki/payloadcms/jobs-queue|Jobs Queue]] — offload heavy work from hooks
|
|
- [[wiki/payloadcms/production|Production & Performance]] — full production deployment guide
|