vite-plugin-vinext-payload
v0.0.7
Published
Vite plugin for running Payload CMS with vinext
Downloads
830
Maintainers
Readme
vite-plugin-vinext-payload
Vite plugin for running Payload CMS with vinext (Cloudflare's Vite-based re-implementation of Next.js).
Experimental. Both vinext and this plugin are experimental. Tested with Payload 3.80.0, vinext 0.0.33, and Vite 8 (Rolldown).
Migrating from Next.js
If you have an existing Payload CMS project on Next.js:
npm install -D vinext vite # Install vinext
npx vinext init # Convert Next.js → vinext
npm install -D vite-plugin-vinext-payload
npx vite-plugin-vinext-payload init # Apply Payload-specific fixes
npm run devNote:
vinext initrunsnpm installinternally. If you hit peer dependency conflicts (common with@vitejs/plugin-react), runnpm install -D vinext vite --legacy-peer-depsbeforenpx vinext init.
The plugin's init command is idempotent — safe to run multiple times. It:
- Adds
payloadPlugin()to yourvite.config.ts - Extracts the inline server function from
layout.tsxinto a separate'use server'module (required for Vite's RSC transform) - Adds
normalizeParamsto the admin page (fixes/admin404)
Use --dry-run to preview changes without writing files.
For Cloudflare D1 projects, see Cloudflare D1 guide for additional configuration.
Quick Start
If you've already run init, or are setting up manually:
// vite.config.ts
import { defineConfig } from "vite";
import vinext from "vinext";
import { payloadPlugin } from "vite-plugin-vinext-payload";
export default defineConfig({
plugins: [vinext(), payloadPlugin()],
});For Cloudflare Workers with RSC:
import { cloudflare } from "@cloudflare/vite-plugin";
import vinext from "vinext";
import { defineConfig } from "vite";
import { payloadPlugin } from "vite-plugin-vinext-payload";
export default defineConfig({
plugins: [
cloudflare({
viteEnvironment: { name: "rsc", childEnvironments: ["ssr"] },
}),
vinext(),
payloadPlugin({
ssrExternal: ["cloudflare:workers"],
}),
],
});Options
payloadPlugin({
// Additional packages to externalize from SSR bundling
ssrExternal: ["some-native-package"],
// Additional packages to exclude from optimizeDeps
excludeFromOptimize: ["some-broken-package"],
// Additional CJS packages needing default export interop
cjsInteropDeps: ["some-cjs-dep"],
});What It Does
payloadPlugin() composes these sub-plugins:
| Plugin | Owner bug | What it does |
| --- | --- | --- |
| payloadUseClientBarrel | Payload | Auto-detects @payloadcms/* barrel files that re-export from 'use client' modules and excludes them from RSC pre-bundling (pre-bundling strips the directive, breaking client references) |
| payloadConfigAlias | workerd | Configures SSR externals — only build tools and native addons are externalized (workerd can't resolve externals at runtime) |
| payloadOptimizeDeps | vinext | Per-environment optimizeDeps: excludes problematic packages, force-includes CJS transitive deps for the client. Auto-discovers all next/* alias specifiers from the resolved config so the optimizer doesn't discover them at runtime (which causes a full page reload) |
| payloadCjsTransform | Vite | Fixes this → globalThis in UMD/CJS wrappers and wraps module.exports with ESM scaffolding (skips React/ReactDOM which Vite 8 handles natively) |
| payloadCliStubs | Payload | Stubs packages not needed at web runtime (console-table-printer, json-schema-to-typescript, esbuild-register, ws) |
| payloadNavHydrationFix | Payload | Patches DefaultNavClient and DocumentTabLink to not switch element types (<a> vs <div>) based on usePathname()/useParams() — prevents React 19 tree-destroying hydration mismatches (AST-based via ast-grep) |
| payloadNavigationHydrationFix | vinext | Patches vinext's next/navigation shim on disk so usePathname/useParams/useSearchParams use client snapshots during hydration instead of the server context (which is null on the client) |
| payloadRedirectFix | vinext | Catches NEXT_REDIRECT errors that leak through the RSC stream during async rendering and converts them to client-side location.replace() redirects |
| payloadRscExportFix | @vitejs/plugin-rsc | Fixes @vitejs/plugin-rsc's CSS export transform dropping exports after sourcemap comments |
| payloadRscStubs | workerd / pnpm | Stubs file-type and drizzle-kit/api for RSC/workerd (Node.js APIs unavailable), polyfills workerd's broken console.createTask |
| payloadServerActionFix | vinext | Moves getReactRoot().render() after the returnValue check in vinext's browser entry so data-returning server actions (like getFormState) don't trigger a re-render that resets Payload's form state. Also rewrites the browser entry's relative shim import to use the pre-bundled alias (AST-based via ast-grep) |
| cjsInterop | Vite | Fixes CJS default export interop for packages like bson-objectid (via vite-plugin-cjs-interop) |
A La Carte
Import individual plugins if you need fine-grained control:
import {
payloadUseClientBarrel,
payloadConfigAlias,
payloadOptimizeDeps,
payloadCjsTransform,
payloadCliStubs,
payloadNavHydrationFix,
payloadNavigationHydrationFix,
payloadRedirectFix,
payloadRscExportFix,
payloadRscStubs,
payloadServerActionFix,
} from "vite-plugin-vinext-payload";Requirements
- Node.js >= 24
- Vite 6, 7, or 8
- vinext 0.0.33+
- Payload CMS 3.x
Known Upstream Issues
These are bugs in dependencies that this plugin works around. See docs/upstream-bugs.md for details and ownership.
| Issue | Owner | Our workaround |
| --- | --- | --- |
| Barrel exports missing 'use client' directive | Payload | Auto-exclude affected packages from RSC optimizeDeps |
| RSC export transform drops exports after sourcemap comments | @vitejs/plugin-rsc | Post-transform newline insertion |
| console.createTask throws "not implemented" | workerd | Try/catch polyfill |
| file-type / drizzle-kit/api unresolvable in workerd | pnpm + Vite | Stub modules for RSC |
| Navigation shim getServerSnapshot returns wrong values during hydration | vinext | Patch on disk to use client snapshots |
| Browser entry imports shims via relative paths → optimizer reload + duplicate React | vinext | Rewrite import to aliased specifier; auto-include all next/* aliases in optimizeDeps |
| render() called before returnValue check → form state reset | vinext | AST transform to reorder render after returnValue |
| Components switch element types based on pathname/params → tree-destroying hydration mismatch | Payload | AST transform to force consistent element types |
| NEXT_REDIRECT errors leak through RSC stream during async rendering | vinext | Client-side redirect interception |
License
MIT
