5.1 KiB
| title | aliases | tags | sources | created | updated | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Payload CMS — Performance Optimization |
|
|
|
2026-05-15 | 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: trueto any field queried frequently → avoids full document scans - See wiki/payloadcms/database-indexes for compound indexes and gotchas
Queries
- Combine multiple query optimizations together:
select,depth,limit,populate - See wiki/payloadcms/queries → Performance section for
depth/selectpatterns
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
- Prevent memory leaks (no unclosed streams, unbounded arrays)
- See wiki/payloadcms/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 → Validation Performance section
Custom Admin Components
- Apply React best practices: memoization, lazy loading, avoid unnecessary re-renders
- Use
@next/bundle-analyzerto identify large components - See wiki/payloadcms/custom-components-authoring → Performance section
Config-Level Optimizations
Block References
Reuse a block definition across multiple collections without duplicating config:
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:
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:
// 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: falseis only available onpayload.db.*— not the Local API.
Frontend Bundle
Tree-shake @payloadcms/ui
Only in non-admin code (front-end imports):
// 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)
{ "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:
// 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: trueis cheap to add, big query speedup - Use
payload.db.updateOnewithreturning: falsewhen you don't need the result back - Block references reduce config size and Admin Panel data transfer
getPayload— never callgetPayload({ 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: falsedramatically reduces cold start time - Frontend bundle: always use full import paths for
@payloadcms/uioutside the Admin Panel
Related
- wiki/payloadcms/database-indexes — single-field and compound indexes
- wiki/payloadcms/queries — depth, select, populate optimization
- wiki/payloadcms/hooks — hook performance patterns
- wiki/payloadcms/jobs-queue — offload heavy work from hooks
- wiki/payloadcms/production — full production deployment guide