Разработка сайта на Payload CMS
Payload CMS — headless CMS на Node.js с TypeScript-first подходом. Конфигурация — код, а не GUI: коллекции, поля, хуки, контроль доступа описываются в TypeScript-файлах. Это означает type safety, рефакторинг через IDE, code review в git. База данных — MongoDB или PostgreSQL через Drizzle ORM. Admin panel генерируется автоматически из конфигурации.
Архитектура проекта
Payload работает как монолит (CMS + frontend в одном Next.js приложении) или в режиме standalone API:
Монолит (рекомендовано):
├── app/ # Next.js App Router
│ ├── (frontend)/ # Публичные страницы
│ └── (payload)/ # Admin panel: /admin
├── payload.config.ts # Конфигурация CMS
├── collections/ # Типы контента
└── globals/ # Глобальные настройки
Standalone режим: Payload как Express/Node.js сервер, фронтенд — отдельный Next.js/Nuxt/Remix.
Создание проекта
npx create-payload-app@latest my-site
# Выбрать: template = website, database = PostgreSQL или MongoDB
cd my-site
npm install
npm run dev
# Admin: http://localhost:3000/admin
Коллекции
Коллекция — тип контента с полями. Аналог модели в Laravel или content type в Drupal:
// collections/Posts.ts
import { CollectionConfig } from 'payload/types'
const Posts: CollectionConfig = {
slug: 'posts',
admin: {
useAsTitle: 'title',
defaultColumns: ['title', 'status', 'publishedAt'],
},
access: {
read: () => true, // публичное чтение
create: ({ req }) => req.user?.role === 'admin',
update: ({ req }) => req.user?.role === 'admin',
delete: ({ req }) => req.user?.role === 'admin',
},
fields: [
{
name: 'title',
type: 'text',
required: true,
},
{
name: 'slug',
type: 'text',
unique: true,
admin: { position: 'sidebar' },
},
{
name: 'content',
type: 'richText',
},
{
name: 'status',
type: 'select',
options: ['draft', 'published'],
defaultValue: 'draft',
admin: { position: 'sidebar' },
},
{
name: 'publishedAt',
type: 'date',
admin: { position: 'sidebar', condition: (_, { status }) => status === 'published' },
},
{
name: 'featuredImage',
type: 'upload',
relationTo: 'media',
},
],
hooks: {
beforeChange: [
({ data }) => {
// Автогенерация slug
if (!data.slug && data.title) {
data.slug = data.title.toLowerCase().replace(/\s+/g, '-').replace(/[^\w-]/g, '')
}
return data
}
],
},
}
export default Posts
Регистрация в payload.config.ts
// payload.config.ts
import { buildConfig } from 'payload/config'
import { postgresAdapter } from '@payloadcms/db-postgres'
import { lexicalEditor } from '@payloadcms/richtext-lexical'
import Posts from './collections/Posts'
import Pages from './collections/Pages'
import Media from './collections/Media'
import Users from './collections/Users'
import Settings from './globals/Settings'
export default buildConfig({
serverURL: process.env.PAYLOAD_PUBLIC_SERVER_URL,
admin: {
user: Users.slug,
bundler: webpackBundler(),
},
editor: lexicalEditor({}),
collections: [Posts, Pages, Media, Users],
globals: [Settings],
db: postgresAdapter({
pool: {
connectionString: process.env.DATABASE_URL,
},
}),
typescript: {
outputFile: path.resolve(__dirname, 'payload-types.ts'),
},
graphQL: {
schemaOutputFile: path.resolve(__dirname, 'generated-schema.graphql'),
},
})
Рендеринг страниц в Next.js
// app/(frontend)/[slug]/page.tsx
import { getPayloadHMR } from '@payloadcms/next/utilities'
import configPromise from '@payload-config'
import { notFound } from 'next/navigation'
export default async function Page({ params }: { params: { slug: string } }) {
const payload = await getPayloadHMR({ config: configPromise })
const result = await payload.find({
collection: 'pages',
where: {
slug: { equals: params.slug },
status: { equals: 'published' },
},
limit: 1,
})
const page = result.docs[0]
if (!page) notFound()
return (
<main>
<h1>{page.title}</h1>
<RichText content={page.content} />
</main>
)
}
export async function generateStaticParams() {
const payload = await getPayloadHMR({ config: configPromise })
const pages = await payload.find({ collection: 'pages', limit: 1000 })
return pages.docs.map(page => ({ slug: page.slug }))
}
Типичный стек проекта
| Слой | Технология |
|---|---|
| CMS + Admin | Payload CMS 2.x |
| Frontend | Next.js 14 App Router |
| База данных | PostgreSQL + Drizzle |
| Медиафайлы | Cloudflare R2 / AWS S3 |
| Поиск | Payload built-in / Algolia |
| Resend / Nodemailer | |
| Деплой | Vercel / Railway / самохост |
Сроки
Базовый сайт с 3–5 типами контента, медиатекой и Next.js фронтендом — 2–3 недели. Сложный проект с кастомными полями, многоязычностью, версионированием и живым превью — 4–8 недель.







