From superpowers
Prunes and patches Payload CMS deployments to Cloudflare Workers to resolve 3 MiB bundle size limits and native dependency issues (sharp, @vercel/og).
How this skill is triggered — by the user, by Claude, or both
Slash command
/superpowers:deploying-payload-to-cloudflareThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Deploying Payload CMS to Cloudflare Workers often hits the 3 MiB bundle size limit and encounters issues with native dependencies like `sharp` and `@vercel/og`. This skill provides a proven strategy to overcome these limitations by pruning unused files and patching imports.
Deploying Payload CMS to Cloudflare Workers often hits the 3 MiB bundle size limit and encounters issues with native dependencies like sharp and @vercel/og. This skill provides a proven strategy to overcome these limitations by pruning unused files and patching imports.
sharp, resvg.wasm, or yoga.wasm.@opennextjs/cloudflare to build the worker.node_modules directory from the output. The worker bundle (handler.mjs) already contains the necessary code (mostly).node_modules) with an empty module stub.By removing node_modules, you are forcing the Worker to rely 100% on the bundled code.
sharp (C++ binary) cannot be bundled. They must exist on disk. Removing node_modules breaks them.
images: { unoptimized: true }) or use external services (e.g., Cloudflare Images).await import('some-lib') and that lib wasn't bundled, it will fail at runtime.
output: 'standalone' is used, which is good at tracing dependencies. Test critical paths.build-worker.sh ScriptCreate a script (e.g., scripts/build-worker.sh) with the following content. This script handles building, pruning, and patching to ensure the worker fits within Cloudflare's limits.
#!/bin/bash
set -e
# 1. Build
# Check if we are being called by OpenNext (which calls 'npm run build' internally)
if [ "$IS_OPENNEXT_BUILD" = "true" ]; then
echo "Running internal Next.js build..."
npm run build:next
exit 0
fi
echo "Building OpenNext worker..."
export IS_OPENNEXT_BUILD=true
npx opennextjs-cloudflare build
# 2. Prune node_modules
# The worker bundle is self-contained enough that we don't need the full node_modules
# But we need to keep some externalized packages like graphql
echo "Pruning node_modules..."
rm -rf .open-next/server-functions/default/node_modules
mkdir -p .open-next/server-functions/default/node_modules
# Copy graphql if it exists, as it's often externalized
if [ -d "node_modules/graphql" ]; then
cp -r node_modules/graphql .open-next/server-functions/default/node_modules/
fi
# 3. Create Empty Stubs
echo "Creating stubs..."
# Stub for WASM modules
echo "export default {};" > .open-next/server-functions/default/empty_wasm.js
# Stub for JS libraries (prettier, sharp, etc.)
echo "export default {};" > .open-next/server-functions/default/empty_lib.js
# 4. Patch handler.mjs
echo "Patching handler.mjs..."
# Detect handler path (CI vs Local structure)
if [ -f ".open-next/server-functions/default/backend/handler.mjs" ]; then
HANDLER_PATH=".open-next/server-functions/default/backend/handler.mjs"
else
HANDLER_PATH=".open-next/server-functions/default/handler.mjs"
fi
if [ ! -f "$HANDLER_PATH" ]; then
echo "Error: $HANDLER_PATH not found!"
exit 1
fi
# Replace absolute paths to WASM files with empty_wasm.js
# Matches both single and double quotes, and optional ?module suffix
perl -pi -e 's|(["'"'"'])[^"'"'"']*node_modules/[^"'"'"']+\.wasm(\?module)?\1|\1./empty_wasm.js\1|g' "$HANDLER_PATH"
# Stub .ttf.bin files (often used by @vercel/og)
perl -pi -e 's|(["'"'"'])[^"'"'"']*node_modules/[^"'"'"']+\.ttf\.bin\1|\1./empty_lib.js\1|g' "$HANDLER_PATH"
# Patch specific problematic libraries (prettier, sharp)
perl -pi -e 's|require\("prettier"\)|require("./empty_lib.js")|g' "$HANDLER_PATH"
perl -pi -e 's|import\("prettier"\)|import("./empty_lib.js")|g' "$HANDLER_PATH"
perl -pi -e 's|require\("sharp"\)|require("./empty_lib.js")|g' "$HANDLER_PATH"
perl -pi -e 's|import\("sharp"\)|import("./empty_lib.js")|g' "$HANDLER_PATH"
# Fix any double-slash issues if they occur
perl -pi -e 's|"/./empty_wasm.js"|"\./empty_wasm.js"|g' "$HANDLER_PATH"
perl -pi -e 's|"/./empty_lib.js"|"\./empty_lib.js"|g' "$HANDLER_PATH"
echo "Build and optimization complete."
next.config.tsAlias problematic dependencies to an empty file during the Next.js build to prevent them from bloating the initial bundle or causing build errors.
// next.config.ts
import { withPayload } from "@payloadcms/next/withPayload";
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
output: "standalone",
images: {
unoptimized: true, // Cloudflare handles images via a separate worker or R2
},
webpack: (config) => {
// Alias sharp and @vercel/og to empty to avoid bundling them
config.resolve.alias.sharp = "./empty.js";
config.resolve.alias["@vercel/og"] = "./empty.js";
return config;
},
};
export default withPayload(nextConfig);
Use Cloudflare's native CI/CD integration by connecting your repository in the Cloudflare Dashboard.
bash scripts/build-worker.sh (or npm run build:worker).open-next/server-functions/defaultnode_modules will almost always exceed the 3 MiB limit.handler.mjs will contain absolute paths to the machine where it was built. These paths will fail at runtime on Cloudflare.pnpm: pnpm's symlinked structure can cause issues with pruning and native module resolution (ERR_DLOPEN_FAILED). Use npm for this strategy to ensure a flat node_modules structure and better compatibility with Cloudflare's build environment.npm run build calls this script, and this script calls npx opennextjs-cloudflare build, which calls npm run build again, you will get an infinite loop. Ensure you have a re-entry guard (as shown in the script above).npx claudepluginhub olsonbd/superpowers-antigravityCreates structured, bite-sized implementation plans from specs or requirements before writing code. Useful for breaking down multi-step tasks into testable steps with file structure and task boundaries.