Admin / Media / Deploy — 運用層
この章のゴール
- Admin UI のカスタマイズ可能ポイントを掴む (ロゴ / Custom Components / theme)
- Plugin エコシステムの全体像と、よく使うプラグインを知る
- Media (Uploads) を ローカル → S3 に切り替える基本パターンを書ける
- Postgres adapter の運用 (db.push vs
payload migrate) を理解する - Jobs Queue でバックグラウンド処理・定期実行を組む最小形を知る
- Vercel / Docker / Railway / Fly へのデプロイの分岐点を持つ
- AI Auto-Embedding の概観を掴み、シリーズを締める
7.1 Admin UI カスタマイズ
公式 doc (Admin Overview) によれば、Admin Panel は TypeScript + React で構築され、Next.js App Router 上の (payload) route group から動作します。admin プロパティを通じてカスタマイズ可能です。
admin プロパティの主要オプション
| オプション | 役割 |
|---|---|
user | 認証 Collection の指定 ('admins' など) |
components | UI 置換 / 差し込み |
livePreview | リアルタイムプレビュー設定 (06 章) |
autoLogin | 開発時の自動ログイン |
avatar | プロフィール画像表示 |
dateFormat | 日付表示形式 |
theme | 強制テーマ ('light' | 'dark' | 'all') |
Custom Components の差し込み
import { buildConfig } from 'payload'
export default buildConfig({
admin: {
user: 'users',
components: {
beforeNavLinks: ['./components/CustomNavBanner'],
afterDashboard: ['./components/DashboardWidget'],
graphics: {
Logo: './components/Logo',
Icon: './components/Icon',
},
},
},
// ...
})
beforeNavLinks / afterNavLinks / beforeDashboard / afterDashboard などの差し込みポイントが用意されており、自分の React コンポーネントを Server / Client いずれの形式でも入れ込めます。graphics.Logo で ロゴ差し替え、custom.scss で CSS オーバーライドという形で white-label ブランディングも実現できます。
3.x 中期以降の Admin 機能 (概観)
3.x の途中で Admin Panel まわりの管理系機能が随時追加されています。深掘りは公式 doc に譲り、存在だけ押さえておくと選定・運用判断に効きます:
- Trash (ソフトデリート) — Collection に
trash: trueを付けると、削除がdeletedAt付きの「ごみ箱入り」になり、Admin UI の Trash ビューから復元・完全削除できます (Trash) - Folders (beta) — Collection 横断でドキュメントをフォルダ分けする整理機能。Collection 側に
folders: true、config 側にfolders設定を書きます (Folders。beta のため minor update で変わる可能性が公式に明記されています) - Query Presets — 一覧のフィルタ・カラム・ソートの組を保存してチームで共有する機能。Collection に
enableQueryPresets: trueで有効化します (Query Presets)
7.2 Plugin エコシステム
公式 doc (Plugins Overview) によれば、Plugin は config.plugins 配列に登録するだけで効きます。Plugin の関数シグネチャは「既存の Config を受け取って変更して返す」純粋関数です:
type Plugin = (incomingConfig: Config) => Config
Payload チーム公式 Plugin (主要)
| Plugin | 用途 |
|---|---|
| Form Builder | フォーム定義を Admin UI で組み立てて、フロントに JSON 配信 |
| SEO | OGP / メタタグ / sitemap 用フィールドの一括追加 |
| Search | 全文検索向け index Collection の自動構築 |
| Nested Docs | 階層構造を持つ Collection (ページツリー等) のサポート |
| Redirects | リダイレクトルールを Admin UI から管理 |
| Stripe | Stripe 連携 (顧客・サブスクリプション同期) |
| Multi-Tenant | マルチテナント (テナントごとにデータを分離) |
| Sentry | エラーレポーティング |
| Import / Export | データのインポート・エクスポート |
| MCP | Model Context Protocol 連携 |
| Ecommerce | E コマース構築用一式 |
| Cloud Storage | 旧来の汎用 storage plugin (S3 単体なら次節の @payloadcms/storage-s3 推奨) |
使い方の例
import { buildConfig } from 'payload'
import { seoPlugin } from '@payloadcms/plugin-seo'
import { searchPlugin } from '@payloadcms/plugin-search'
export default buildConfig({
collections: [/* ... */],
plugins: [
seoPlugin({
collections: ['posts', 'pages'],
generateTitle: ({ doc }) => `${doc.title} | My Site`,
}),
searchPlugin({
collections: ['posts'],
}),
],
})
Plugin は Config が正規化される前に走るので、自分の Plugin で Collection を追加したり、既存 Collection に Hook を注入したりできます。
7.3 Media (Uploads) — ローカル vs S3
Upload-enabled Collection の基本
公式 doc (Upload Overview) によれば、upload プロパティを付けると、Collection は ファイルアップロード機能を持ち、filename / mimeType / filesize が自動で追加されます。
import path from 'path'
export const Media: CollectionConfig = {
slug: 'media',
upload: {
staticDir: path.resolve(__dirname, '../media'),
mimeTypes: ['image/*'],
imageSizes: [
{ name: 'thumbnail', width: 400, height: 300, position: 'centre' },
{ name: 'card', width: 768, height: undefined }, // 高さ自動
{ name: 'hero', width: 1920, height: 1080 },
],
adminThumbnail: 'thumbnail',
focalPoint: true,
},
fields: [
{ name: 'alt', type: 'text' },
],
}
imageSizes の自動リサイズ
公式 doc によれば、imageSizes 配列に定義したサイズが Sharp ライブラリで自動生成されます。height: undefined でアスペクト比保持、position でクロップ位置、withoutEnlargement で「元画像より小さい場合の挙動」を制御できます。
S3 への切り替え
@payloadcms/storage-s3 プラグインで S3 (および S3 互換ストレージ) に切り替えられます。これは Payload 2.x 時代に必要だった @payloadcms/plugin-cloud-storage の後継です。
import { s3Storage } from '@payloadcms/storage-s3'
export default buildConfig({
collections: [Media],
plugins: [
s3Storage({
collections: {
media: true, // Media collection を S3 に
},
bucket: process.env.S3_BUCKET!,
config: {
region: process.env.S3_REGION!,
credentials: {
accessKeyId: process.env.S3_ACCESS_KEY!,
secretAccessKey: process.env.S3_SECRET_KEY!,
},
},
clientUploads: true, // Vercel の 4.5MB body 制限を回避 (後述)
}),
],
})
config は @aws-sdk/client-s3 の S3ClientConfig をそのまま渡せるため、Cloudflare R2 / DigitalOcean Spaces / Backblaze B2 など S3 互換のあらゆるストレージを同じ syntax で使えます。
バケット自体の設計 (Block Public Access・暗号化・バージョニング・CORS・presigned URLの前提となる設定) は、AWS実践ガイド 第5章: S3 で扱っています。
clientUploads の意義
Vercel のサーバ関数には body サイズに 4.5MB 制限があり、大きい画像を Admin UI からアップロードすると失敗します。clientUploads: true を付けると ブラウザから直接 S3 へ署名付き URL でアップロードされるため、サーバを経由せず制限を回避できます。Vercel 上で運用する場合の事実上の必須設定です。
7.4 DB adapter の選定
| adapter | パッケージ | 用途・特徴 |
|---|---|---|
| Postgres | @payloadcms/db-postgres | 3.x で stable / Drizzle ORM + node-postgres / 本シリーズの主軸 |
| Vercel Postgres | @payloadcms/db-vercel-postgres | Vercel Postgres に最適化 |
| MongoDB | @payloadcms/db-mongodb | Payload 1.x からの伝統 / スキーマ柔軟 |
| SQLite | @payloadcms/db-sqlite | ローカル開発 / 小規模運用 / トランザクションはデフォルト無効 |
迷ったら 本番 RDB を持ちたいなら Postgres、スキーマ進化が多いなら MongoDB、開発機で完結させたいなら SQLite が基本指針です。
7.5 Migration 戦略
公式 doc (Migrations) によれば、Postgres / SQLite adapter は Drizzle ORM の migration 機構を流用しています。
コマンド一覧
| コマンド | 用途 |
|---|---|
payload migrate | 未実行の migration を順に適用 |
payload migrate:create | 新しい migration ファイルを生成 (現在のスキーマ差分から) |
payload migrate:status | 適用状況の確認 |
payload migrate:down | 最後のバッチをロールバック |
payload migrate:refresh | 全ロールバック後に再適用 |
payload migrate:reset | 全 migration ロールバック |
payload migrate:fresh | DB を完全リセットして再構築 |
Migration ファイル
// migrations/20260616_add_posts_status.ts
import type { MigrateUpArgs, MigrateDownArgs } from '@payloadcms/db-postgres'
export async function up({ payload, req }: MigrateUpArgs): Promise<void> {
// スキーマ変更を実行
}
export async function down({ payload, req }: MigrateDownArgs): Promise<void> {
// up を巻き戻す処理
}
dev push vs production migrate
| モード | 適用先 | 用途 |
|---|---|---|
db push (db.push: true、デフォルト dev で有効) | ローカル DB | スキーマ変更を即時反映、migration ファイル不要 |
payload migrate | 本番 DB | CI/CD で実行、migration ファイル必須 |
公式 doc は **「push と migrate を同じローカル DB で混在させないこと」**を明示警告しています。ローカルでは push で素早く回し、リリース前に migrate:create で migration ファイルを切る、というフローが標準です。
CI/CD の典型形
// package.json
{
"scripts": {
"build": "next build",
"ci": "payload migrate && pnpm build"
}
}
デプロイの前段で payload migrate を必ず走らせることで、production DB がスキーマ更新を取り逃さない構成にできます。
7.6 Jobs Queue (バックグラウンド処理)
メール送信・外部 API 連携・重い集計のような「リクエストの外で走らせたい処理」のために、PayloadCMS は Jobs Queue を core 機能として持っています (Jobs Queue (公式 doc))。外部のジョブ基盤を別途立てずに、config だけで task 定義から定期実行まで完結します。
Task の定義と queue
jobs.tasks に slug / inputSchema / handler を定義し、payload.jobs.queue() で積みます:
// payload.config.ts
export default buildConfig({
// ...
jobs: {
tasks: [
{
slug: 'sendEmail',
inputSchema: [
{ name: 'userId', type: 'text', required: true },
],
handler: async ({ input, req }) => {
const user = await req.payload.findByID({
collection: 'users',
id: input.userId,
})
await req.payload.sendEmail({
to: user.email,
subject: 'Welcome!',
html: '...',
})
return { output: {} }
},
},
],
},
})
// Server Action / Hook / カスタムエンドポイントから積む
await payload.jobs.queue({
task: 'sendEmail',
input: { userId: '123' },
waitUntil: new Date('2026-12-25T00:00:00Z'), // (任意) 実行を遅らせる
})
cron スケジュールと autoRun
task 定義に schedule を付けると cron 周期で queue に積まれ、autoRun の runner が処理します:
jobs: {
tasks: [
{
slug: 'dailyDigest',
schedule: [{ cron: '0 8 * * *', queue: 'daily' }], // 毎日 8:00 に積む
handler: async ({ req }) => {
/* ... */
return { output: {} }
},
},
],
autoRun: [
{ cron: '* * * * *', queue: 'daily', limit: 10 }, // 毎分 queue を確認して実行
],
}
schedule (積む側) と autoRun (実行する側) は同じ queue 名を指す必要があります。
serverless (Vercel) での注意
公式 doc は autoRun を「常時起動している専用サーバ向け」とし、Vercel のような serverless プラットフォームでは使わないよう明示しています (Queues (公式 doc))。serverless では、ジョブ実行用に用意される GET /api/payload-jobs/run エンドポイントを外部スケジューラから定期的に叩きます。Vercel なら vercel.json の crons にこのパスを登録し、環境変数 CRON_SECRET を設定すると Authorization: Bearer ${CRON_SECRET} が自動送信されてエンドポイントが保護されます。常駐 Node サーバ (7.7 節の Docker / VPS) なら autoRun がそのまま機能します。
複数 task の連鎖 (workflows) やリトライ制御などの詳細は公式 doc に一式あります。本節は「Jobs Queue が core にある」ことを押さえれば十分です。
7.7 デプロイ — 4 つの選択肢
PayloadCMS は Node.js が動くなら基本どこでもデプロイ可能 (Edge は不可、06 章)。主な選択肢を比較します。
Vercel
最も手早い選択肢。Vercel Postgres + @payloadcms/db-vercel-postgres で完結します。
注意点:
- Function timeout: Vercel Functions は fluid compute (デフォルト有効) で デフォルト 300 秒 (5 分)。上限は Hobby 300 秒 / Pro・Enterprise 800 秒、ベータの extended max duration を使うと最大 1,800 秒 (30 分) (Vercel: Configuring Maximum Duration、2026-07 時点)。大量データの import / migrate には注意
- Body 4.5MB 制限: Admin UI から大きな画像をアップすると失敗する。
@payloadcms/storage-s3のclientUploads: trueで回避 (7.3 節) - Cold start: function ベースのため、長時間アクセスがないと初回 request が遅くなる
// vercel.json
{
"functions": {
"app/(payload)/api/**/route.ts": { "maxDuration": 60 }
}
}
Docker / 自前 Node サーバ
editor が頻繁にアクセスする用途・Long-running 処理がある場合は VPS 上の Node サーバが向きます。Cold start なし、function timeout なし、body サイズ制限なし。
公式 doc が示す Docker 例 (node:24-alpine、multi-stage、output: 'standalone' 使用):
# 抜粋
FROM node:24-alpine AS base
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
COPY . .
RUN yarn build
FROM node:24-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY /app/.next/standalone ./
COPY /app/.next/static ./.next/static
COPY /app/public ./public
CMD ["node", "server.js"]
next.config に output: 'standalone' を付けるのを忘れないこと。
Railway / Fly / Render
「managed container」系。Docker と Vercel の中間的な選択肢で、container を build → 立ち上げてくれる、コストは Vercel より読みやすい、timeout 制約なし。Payload も「Next.js が動くところならどこでも動く」と公式が表明しています。
Cloudflare Workers (D1 + R2)
2025 年 10 月 3 日に Cloudflare Workers へのワンクリックデプロイ対応が公開されました (公式ブログ)。公式テンプレート with-cloudflare-d1 が用意され、Workers + D1 (DB) + R2 (Storage) の組み合わせで動かせます。
制約:
- Workers Paid Plan 必須 (Free は 3MB の Workers サイズ制限を超えるため不可)
- pino-pretty が Node API に依存するため、custom console logger に置き換える必要あり
- GraphQL は upstream の Workers 側 issue で現状動かないケースあり (template 同梱の解説参照)
- Page Router 系の Pages (旧 Cloudflare Pages) は対象外、Workers 経由のデプロイ
「Edge runtime で動かない」という制約 (06 章 6.5) は本質として残るため、Cloudflare 上でも内部的には Node 互換ランタイムを介した形になります。本シリーズでは要件が合えば検討、というスタンスです。
7.8 Payload Cloud (新規受付停止中)
Figma 買収 (2025 年 6 月) 以降、Payload Cloud の新規 sign-up は停止しています。既存ユーザーはアクセスを継続できますが、新規プロジェクトは self-host を前提に上記 4 つの選択肢から選ぶ必要があります (出典: Payload is joining Figma! (公式ブログ) / GitHub Discussion #12843)。
OSS / self-host が PayloadCMS の主軸であることを考えると、Payload Cloud に依存しない方が長期的には安全な選択です。
7.9 AI Auto-Embedding (概観)
公式 doc トップ (What is Payload) には、エンタープライズ機能として AI Auto-Embedding が列挙されています。Collection のコンテンツを ベクトル埋め込みとして自動生成し、RAG (Retrieval-Augmented Generation) のデータソースとして使える機能です。
OSS コアで RAG 連携を組む場合、自前で:
- Hook (afterChange) でテキスト抽出
- 埋め込み API (OpenAI / Cohere / Voyage 等) を叩く
- ベクトル DB (pgvector / Pinecone / Weaviate 等) に保存
- 検索エンドポイントを
endpointsで生やす
という構成も可能ですが、エンタープライズ版なら一気に省力化できる、という位置付けです。
7.10 環境変数の整理
PayloadCMS を本番で動かす場合の最小セット:
# Payload 本体
PAYLOAD_SECRET=本番用に十分長いランダム文字列 (32 文字以上推奨)
DATABASE_URL=postgresql://user:pass@host:5432/db
PAYLOAD_PUBLIC_SERVER_URL=https://example.com
# プレビュー
PREVIEW_SECRET=draft preview 用の独自トークン
# Media (S3 使う場合)
S3_BUCKET=my-bucket
S3_REGION=us-east-1
S3_ACCESS_KEY=...
S3_SECRET_KEY=...
# Next.js 公開側
NEXT_PUBLIC_SERVER_URL=https://example.com
PAYLOAD_SECRET は 十分長くランダムにすること、PAYLOAD_CONFIG_PATH は config を非標準位置に置いている場合のみ。
実装例 — Postgres migration の raw SQL 実行 (up / down)
payload migrate:create で生成される migration ファイルは、up と down の 2 関数を export する形になっています。Postgres adapter では、sql template literal を db.execute() に渡すことで生 SQL を流せます (Migrations doc)。
// migrations/20260616_100000_add_archived_flag.ts
import { type MigrateUpArgs, type MigrateDownArgs, sql } from '@payloadcms/db-postgres'
export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
// 既存のスキーマを payload generate:db-schema で更新 → migrate:create で反映するのが基本だが、
// データ補正・index 追加など raw SQL が必要なケースで sql テンプレートを使う
await db.execute(sql`
ALTER TABLE posts
ADD COLUMN archived BOOLEAN NOT NULL DEFAULT false
`)
// 既存データを Local API で touch する例 (公式 doc は Postgres の例で `SELECT * from posts` を示している)
const { rows: posts } = await db.execute(sql`SELECT id FROM posts WHERE _status = 'published'`)
payload.logger.info({ migrated: posts.length }, 'archived flag initialized')
}
export async function down({ db, payload, req }: MigrateDownArgs): Promise<void> {
await db.execute(sql`
ALTER TABLE posts
DROP COLUMN archived
`)
}
ポイント:
import { sql } from '@payloadcms/db-postgres'— Postgres adapter 由来の sql template literal (SQLite なら@payloadcms/db-sqliteから同名で取れる)db.execute()にsqltemplate literal を渡して SQL を実行、戻り値は{ rows: T[] }- down 関数で up を巻き戻す処理を書く — production で問題発生時にロールバック可能に
payload.loggerで migration の進捗をログに残す (04 章 監査ログパターン と同じ pino style)
公式 doc は raw SQL を流す例 (上記) を主に示しており、Drizzle ORM の query builder API を使った migration の完全例は未掲載です。型安全に書きたい場合は Drizzle 公式 doc を併読してください。
この章のまとめ
- Admin UI は
admin.componentsで部分置換、graphics.Logoでブランディング、custom.scssで全体カスタマイズ - Plugin は
config.plugins配列に並べるだけ、公式に SEO / Form Builder / Search / Stripe / Multi-Tenant / Sentry など多数 - Media は ローカル → S3 (
@payloadcms/storage-s3) にs3Storage()1 行で切り替え可、Vercel ではclientUploads: trueが事実上必須 - Postgres adapter は dev push と
payload migrateを 絶対に混在させない、CI で migrate を必ず実行 - デプロイは Vercel / Docker / Railway 系 / 自前 Node から目的別に選ぶ。Edge runtime は基本不可、Cloudflare は Workers (Paid + 制約) で対応
- Jobs Queue が core にある: task 定義 +
payload.jobs.queue()+schedule/autoRun。serverless では autoRun でなく/api/payload-jobs/runを外部 cron から叩く - Admin 系は 3.x 中期以降 Trash / Folders (beta) / Query Presets が追加 — 必要になったら公式 doc へ
- Payload Cloud は 2025-06 以降新規受付停止、self-host が標準
- AI Auto-Embedding はエンタープライズ機能、OSS でも Hook + ベクトル DB で自前実装可
- Migration は
import { sql } from '@payloadcms/db-postgres'+db.execute()にsqltemplate literal を渡して raw SQL を流せる (up / down を対称に書く)
シリーズのまとめ
7 章を通して、PayloadCMS を「Next.js プロジェクトに同居させて、データモデルから運用まで TypeScript 1 枚で書く」道筋を辿ってきました。
- 01 PayloadCMS とは / なぜ今
- 02 Core Concepts
- 03 Data Modeling
- 04 Security & Lifecycle
- 05 APIs
- 06 Next.js Integration
- 07 Admin / Media / Deploy (本章)
次のステップは、自分のプロジェクトで create-payload-app を走らせて、最小の Collection 1 つから始めることです。公式 doc の各機能ページを横に置きながら、本シリーズで掴んだ全体像と突き合わせて読むと、知識が定着しやすいはずです。