obsidian/wiki/payloadcms/performance-overview.md
2026-05-15 16:15:30 +01:00

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