Documentation Index Fetch the complete documentation index at: https://mintlify.com/cloudflare/vinext/llms.txt
Use this file to discover all available pages before exploring further.
Deploys your Next.js application to Cloudflare Workers with automatic configuration generation. Handles App Router and Pages Router projects with zero config required.
Usage
Options
Deploy to a preview environment instead of production. Creates a temporary preview URL for testing before production deployment.
Custom Worker name. Defaults to project name from package.json. vinext deploy --name my-custom-name
Worker names must be lowercase alphanumeric with hyphens.
Skip the build step and deploy existing dist/ output. vinext deploy --skip-build
Useful when you’ve already built and want to deploy quickly.
Generate config files without building or deploying. Shows what files would be created: wrangler.jsonc, worker/index.ts, vite.config.ts.
Show help for this command. Can also use -h.
Experimental: Traffic-aware Pre-Rendering (TPR)
TPR is experimental and must be explicitly enabled. Requires a custom domain (zone analytics unavailable on *.workers.dev) and CLOUDFLARE_API_TOKEN with Zone.Analytics read permission.
Enable Traffic-aware Pre-Rendering using Cloudflare zone analytics. vinext deploy --experimental-tpr
Pre-renders hot pages into KV cache during deployment based on actual traffic data.
Traffic coverage target percentage (0-100). vinext deploy --experimental-tpr --tpr-coverage 95
Pre-renders enough pages to cover 95% of actual traffic.
Hard cap on number of pages to pre-render. vinext deploy --experimental-tpr --tpr-limit 500
Prevents excessive KV usage on high-traffic sites.
Analytics lookback window in hours. vinext deploy --experimental-tpr --tpr-window 48
Uses the last 48 hours of traffic data to determine hot pages.
What It Does
The deploy command automates the entire Cloudflare Workers deployment:
// From deploy.ts:698-784
export async function deploy ( options : DeployOptions ) : Promise < void > {
// 1. Detect project structure (App/Pages Router, ISR usage, etc.)
const info = detectProject ( root );
// 2. Install missing dependencies
const missingDeps = getMissingDeps ( info );
if ( missingDeps . length > 0 ) {
installDeps ( root , missingDeps );
}
// 3. Ensure ESM (add "type": "module" to package.json)
if ( ! info . hasTypeModule ) {
renameCJSConfigs ( root ); // Rename next.config.js → next.config.cjs
ensureESModule ( root ); // Add "type": "module"
}
// 4. Generate config files if missing
const filesToGenerate = getFilesToGenerate ( info );
writeGeneratedFiles ( filesToGenerate );
// 5. Build for Cloudflare Workers
if ( ! options . skipBuild ) {
await runBuild ( info );
}
// 6. Optional: Pre-render hot pages (TPR)
if ( options . experimentalTPR ) {
await runTPR ({ root , coverage , limit , window });
}
// 7. Deploy via wrangler
const url = runWranglerDeploy ( root , options . preview );
console . log ( `Deployed to: ${ url } ` );
}
1. Project Detection
Scans your project to determine:
Router type : App Router vs Pages Router
ISR usage : Detects export const revalidate in pages
MDX usage : Checks for .mdx files or @next/mdx config
Native modules : Detects modules that need stubbing (@resvg/resvg-js, satori, etc.)
Existing config : Checks for wrangler.jsonc, worker/index.ts, vite.config.ts
2. Dependency Installation
Automatically installs required packages if missing:
@cloudflare/vite-plugin (always required)
wrangler (Cloudflare Workers CLI)
@vitejs/plugin-rsc (App Router only)
@mdx-js/rollup (if MDX detected)
// From deploy.ts:547-570
export function getMissingDeps ( info : ProjectInfo ) : MissingDep [] {
const missing : MissingDep [] = [];
if ( ! info . hasCloudflarePlugin ) {
missing . push ({ name: "@cloudflare/vite-plugin" , version: "latest" });
}
if ( ! info . hasWrangler ) {
missing . push ({ name: "wrangler" , version: "latest" });
}
if ( info . isAppRouter && ! info . hasRscPlugin ) {
missing . push ({ name: "@vitejs/plugin-rsc" , version: "latest" });
}
if ( info . hasMDX && ! hasMdxRollup ) {
missing . push ({ name: "@mdx-js/rollup" , version: "latest" });
}
return missing ;
}
3. ESM Configuration
Cloudflare Workers requires ESM. vinext automatically:
Renames CJS config files:
next.config.js → next.config.cjs
postcss.config.js → postcss.config.cjs
Adds "type": "module" to package.json
4. Config File Generation
Generates missing files:
Cloudflare Workers configuration: {
"$schema" : "node_modules/wrangler/config-schema.json" ,
"name" : "my-app" ,
"compatibility_date" : "2026-02-25" ,
"compatibility_flags" : [ "nodejs_compat" ],
"main" : "./worker/index.ts" ,
"assets" : {
"not_found_handling" : "none" ,
"binding" : "ASSETS"
},
"images" : {
"binding" : "IMAGES"
}
}
If ISR is detected, adds KV namespace: "kv_namespaces" : [
{
"binding" : "VINEXT_CACHE" ,
"id" : "<your-kv-namespace-id>"
}
]
worker/index.ts (App Router)
Worker entry for App Router with image optimization: import { handleImageOptimization } from "vinext/server/image-optimization" ;
import handler from "vinext/server/app-router-entry" ;
interface Env {
ASSETS : Fetcher ;
IMAGES : {
input ( stream : ReadableStream ) : {
transform ( options : Record < string , unknown >) : {
output ( options : { format : string ; quality : number }) : Promise <{ response () : Response }>;
};
};
};
}
export default {
async fetch ( request : Request , env : Env ) : Promise < Response > {
const url = new URL ( request . url );
// Image optimization via Cloudflare Images binding
if ( url . pathname === "/_vinext/image" ) {
return handleImageOptimization ( request , {
fetchAsset : ( path ) => env . ASSETS . fetch ( new Request ( new URL ( path , request . url ))),
transformImage : async ( body , { width , format , quality }) => {
const result = await env . IMAGES . input ( body ). transform ( width > 0 ? { width } : {}). output ({ format , quality });
return result . response ();
},
});
}
// Delegate everything else to vinext
return handler . fetch ( request );
} ,
} ;
worker/index.ts (Pages Router)
Worker entry for Pages Router: import { handleImageOptimization } from "vinext/server/image-optimization" ;
// @ts-expect-error — virtual module resolved by vinext at build time
import { renderPage , handleApiRoute } from "virtual:vinext-server-entry" ;
interface Env {
ASSETS : Fetcher ;
IMAGES : { /* ... */ };
}
export default {
async fetch ( request : Request , env : Env ) : Promise < Response > {
const url = new URL ( request . url );
const pathname = url . pathname ;
const urlWithQuery = pathname + url . search ;
// Block protocol-relative URL open redirect attacks (//evil.com/)
if ( pathname . startsWith ( "//" )) {
return new Response ( "404 Not Found" , { status: 404 });
}
// Image optimization
if ( pathname === "/_vinext/image" ) {
return handleImageOptimization ( request , { /* ... */ });
}
// API routes
if ( pathname . startsWith ( "/api/" ) || pathname === "/api" ) {
return await handleApiRoute ( request , urlWithQuery );
}
// Page routes
return await renderPage ( request , urlWithQuery , null );
} ,
} ;
Vite configuration with Cloudflare plugin: import { defineConfig } from "vite" ;
import vinext from "vinext" ;
import { cloudflare } from "@cloudflare/vite-plugin" ;
export default defineConfig ({
plugins: [
vinext (),
cloudflare ({
viteEnvironment: {
name: "rsc" ,
childEnvironments: [ "ssr" ],
},
}),
] ,
}) ;
If native modules are detected, adds stub aliases: resolve : {
alias : {
"@resvg/resvg-js" : path . resolve ( __dirname , "empty-stub.js" ),
"satori" : path . resolve ( __dirname , "empty-stub.js" ),
},
},
5. Build for Workers
Runs the Vite build with Cloudflare-specific configuration:
// From deploy.ts:644-660
async function runBuild ( info : ProjectInfo ) : Promise < void > {
if ( info . isAppRouter ) {
// Multi-environment build for App Router
const builder = await createBuilder ({ root: info . root });
await builder . buildApp ();
} else {
// Single build for Pages Router
await build ({ root: info . root });
}
}
The @cloudflare/vite-plugin handles Worker-specific bundling.
6. Deploy via Wrangler
Executes wrangler deploy to publish to Cloudflare:
// From deploy.ts:664-694
function runWranglerDeploy ( root : string , preview : boolean ) : string {
const args = preview ? [ "deploy" , "--env" , "preview" ] : [ "deploy" ];
const output = execSync ( `wrangler ${ args . join ( " " ) } ` , {
cwd: root ,
stdio: "pipe" ,
encoding: "utf-8" ,
});
// Parse deployed URL from output
const urlMatch = output . match ( /https: \/\/ [ ^ \s ] + \. workers \. dev [ ^ \s ] * / );
return urlMatch ? urlMatch [ 0 ] : "(URL not detected)" ;
}
Examples
First Deployment
Deploy to production:
Output:
vinext deploy (Vite 6.0.0)
Project: my-app
Router: App Router
ISR: none
Installing: @cloudflare/vite-plugin, wrangler, @vitejs/plugin-rsc
Created wrangler.jsonc
Created worker/index.ts
Created vite.config.ts
Added "type": "module" to package.json
Building for Cloudflare Workers...
✓ Built RSC environment in 3.2s
✓ Built SSR environment in 2.1s
✓ Built client environment in 4.5s
Deploying to production...
✓ Published my-app (version_id: abc123)
─────────────────────────────────────────
Deployed to: https://my-app.example.workers.dev
─────────────────────────────────────────
Preview Deployment
Deploy to preview environment for testing:
Creates a temporary preview URL: https://my-app-preview.example.workers.dev
Custom Worker Name
Deploy with a custom name:
vinext deploy --name my-custom-name
Dry Run (See What Would Be Generated)
Output:
Project: my-app
Router: App Router
ISR: none
Created wrangler.jsonc
Created worker/index.ts
Created vite.config.ts
Dry run complete. Files generated but no build or deploy performed.
Skip Build (Deploy Existing Build)
Useful for quick redeployment:
vinext build
vinext deploy --skip-build
Traffic-aware Pre-Rendering
Pre-render hot pages based on analytics:
vinext deploy --experimental-tpr
With custom coverage target:
vinext deploy --experimental-tpr --tpr-coverage 95 --tpr-limit 500
ISR (Incremental Static Regeneration)
If your app uses ISR, vinext automatically:
Detects export const revalidate in pages
Adds KV namespace binding to wrangler.jsonc
Configures runtime cache handler
// app/blog/[slug]/page.tsx
export const revalidate = 3600 ; // Revalidate every hour
export default async function BlogPost ({ params }) {
const post = await fetchPost ( params . slug );
return < article >{post. content } </ article > ;
}
After deployment, you need to create the KV namespace:
wrangler kv:namespace create VINEXT_CACHE
# ➜ Created namespace "VINEXT_CACHE"
# ➜ ID: abc123def456
Update wrangler.jsonc:
{
"kv_namespaces" : [
{
"binding" : "VINEXT_CACHE" ,
"id" : "abc123def456"
}
]
}
Redeploy:
Custom Domains
To use a custom domain:
Add your domain in Cloudflare dashboard
Add route to wrangler.jsonc:
{
"routes" : [
{
"pattern" : "example.com/*" ,
"custom_domain" : true
}
]
}
Deploy:
Environment Variables
Set secrets using wrangler:
wrangler secret put DATABASE_URL
# Enter the secret value: postgresql://...
Or bulk upload from .env.production:
wrangler secret bulk .env.production
Access in your app:
// App Router: route handlers have access to env
export async function GET ( request : Request , { env }) {
const dbUrl = env . DATABASE_URL ;
// ...
}
CI/CD Integration
GitHub Actions
name : Deploy to Cloudflare Workers
on :
push :
branches : [ main ]
jobs :
deploy :
runs-on : ubuntu-latest
steps :
- uses : actions/checkout@v4
- uses : actions/setup-node@v4
with :
node-version : 20
- run : npm ci
- run : npx vinext deploy
env :
CLOUDFLARE_API_TOKEN : ${{ secrets.CLOUDFLARE_API_TOKEN }}
GitLab CI
deploy :
stage : deploy
image : node:20
script :
- npm ci
- npx vinext deploy
environment :
name : production
url : https://my-app.example.workers.dev
only :
- main
Troubleshooting
”No wrangler found”
vinext installs wrangler automatically, but if you see this error:
npm install -D wrangler
vinext deploy
“Invalid Worker name”
Worker names must be lowercase alphanumeric with hyphens:
# Invalid
vinext deploy --name My_App
# Valid
vinext deploy --name my-app
Build Fails with Native Module Error
Some native Node.js modules don’t work in Workers. vinext automatically stubs common ones:
@resvg/resvg-js
satori
lightningcss
@napi-rs/canvas
sharp
If you need a stubbed module, create empty-stub.js:
Deploy Fails with “Unauthorized”
Set your Cloudflare API token:
export CLOUDFLARE_API_TOKEN = your_token_here
vinext deploy
Get a token from: https://dash.cloudflare.com/profile/api-tokens
Required permissions:
Account.Workers Scripts (Edit)
Account.Workers KV Storage (Edit, if using ISR)
Pricing
Cloudflare Workers Free tier:
100,000 requests/day
10ms CPU time per request
Unlimited bandwidth
Unlimited storage (KV: 1GB free)
Paid tier ($5/month):
10 million requests/month (included)
50ms CPU time per request
$0.50 per million requests after
KV: 10GB included
Most Next.js apps stay within the free tier during development and small-scale production use.
Next Steps
Cloudflare Deployment Deep dive into Cloudflare Workers deployment
check Command Check your project for compatibility