Payloadflare is a starter for building content-managed websites with Payload CMS and Next.js, designed to be hosted on Cloudflare Workers.
It is derived from Payload's official Cloudflare D1 template.
Important: This starter currently exceeds the Cloudflare Workers Free plan's script-size limit, so deployment requires a paid Workers plan.
It includes authenticated admin users, public media uploads, a form builder, and a blank frontend. Data is stored in D1 and media in R2.
Requirements: Node.js 24.15.0 or later, pnpm, and a Cloudflare account.
pnpm install
pnpm wrangler loginCreate a .env file with:
# Generate a secret with `openssl rand -hex 32`.
PAYLOAD_SECRET=replace-with-a-random-secret
NEXT_PUBLIC_SERVER_URL=http://localhost:3000Start the app:
pnpm dev- Website:
http://localhost:3000 - Payload admin:
http://localhost:3000/admin
To bring template improvements into a project created from this repository, add this repository as an upstream remote once:
git remote add upstream https://github.com/larussverris/payloadflare.gitThen fetch and merge template updates when needed:
git fetch upstream
git merge upstream/mainResolve any merge conflicts, then run the relevant checks before deploying.
Replace the placeholder names and IDs in wrangler.jsonc with your Worker, D1, and R2 resources.
The app uses:
D1for Payload dataR2for Payload media uploadsNEXT_INC_CACHE_R2_BUCKETfor the Next.js incremental cacheNEXT_TAG_CACHE_D1for cache-tag invalidationNEXT_CACHE_DO_QUEUEto coordinate ISR revalidationWORKER_SELF_REFERENCEas the Worker's self-service bindingEMAILto send transactional email through Cloudflare Email
Set defaultFromAddress and defaultFromName in the email adapter configuration in
src/payload.config.ts. These values are configured in code, not through environment variables.
Keep defaultFromAddress in sync with allowed_sender_addresses on the EMAIL binding in
wrangler.jsonc. The sender address must use a domain onboarded to Cloudflare Email. Onboard the
domain before sending mail:
pnpm wrangler email sending enable example.comThe @payloadflare/email-cloudflare workspace package is configured in src/payload.config.ts, so auth emails and payload.sendEmail(...) use the EMAIL binding.
The first-primary strategy is enabled in src/payload.config.ts, but replicas must also be enabled on the database itself:
- Open the D1 database in the Cloudflare dashboard.
- Go to Settings.
- Enable Read Replication.
Connect the media R2 bucket to a custom domain such as assets.yoursite.com, then set MEDIA_ORIGIN to that URL in the Cloudflare Worker environment. The app fails fast in production if it is missing.
Cloudflare's default Browser Cache TTL is four hours. Under Caching → Cache Rules, create a rule named R2 media - 30 day cache that keeps uploaded images and videos in both Cloudflare's edge cache and visitors' browser caches for longer.
Match the media hostname and image or video extensions:
(http.host eq "assets.yoursite.com" and http.request.uri.path.extension in {"avif" "gif" "jpg" "jpeg" "png" "svg" "webp" "mp4" "webm"})
Use these settings:
- Cache eligibility: Eligible for cache
- Edge TTL: Ignore cache-control header and use 30 days
- Browser TTL: Override origin and use 1 hour
A Cloudflare cache purge cannot remove files already stored in visitors' browsers. When replacing a media file, use a new or versioned filename so its URL changes.
Before deploying schema changes, create a Payload migration:
pnpm payload migrate:createDeploy the database migration and application together:
pnpm deploy- Metadata caching keeps
robots.txtandllms.txton themaxcache profile andsitemap.xmlon thehoursprofile. - Persistent Next.js cache stores SSG, ISR, and data-cache entries in R2.
- Regional cache keeps frequently read cache entries close to the Worker for up to one minute.
- D1 read replicas use Payload's
first-primarystrategy for consistent reads with lower latency after the initial primary query. - On-demand invalidation is supported through D1-backed Next.js cache tags when application code revalidates tagged content.
- Deduplicated revalidation uses a Durable Object queue to avoid repeated ISR work.
- Cloudflare image transformations resize images at
/cdn-cgi/image, negotiate the output format automatically, and default to quality 85. - Direct media delivery serves production uploads from the configured media hostname instead of proxying them through Payload.
- Static asset caching keeps Next.js's hashed
/_next/static/*files immutable for one year and manually managed/static/*files cached for 30 days. - React Compiler is enabled for automatic React rendering optimizations.
- Worker-aware bundling keeps
joseandpg-cloudflareexternal for the workerd runtime. - Production logging writes structured JSON through
console, which integrates with Cloudflare Workers logs.
Put manually managed assets in public/static/ (create the directory when needed):
public/static/logo.svg -> /static/logo.svg
Everything under /static/* is served with:
Cache-Control: public,max-age=2592000These files are cached by browsers for 30 days. Versioned filenames such as logo-v2.svg or logo.abcd1234.svg are still useful when an update must appear immediately.
Other files placed directly in public/ use Cloudflare's normal static-asset caching unless another rule is added to public/_headers.
Uploads are limited to 6 MiB. Payload's Sharp-based crop and focal-point tools are disabled because Sharp is not supported in the Workers runtime.
