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