Loading...
Loading...
Vite plugin that reimplements the Next.js API surface for deploying anywhere, including Cloudflare Workers
npx skill4agent add aradotso/trending-skills vinext-vite-nextjsSkill by ara.so — Daily 2026 Skills collection.
next/*# Automated one-command migration
npx vinext initvinext checkvite@vitejs/plugin-react@vitejs/plugin-rscreact-server-dom-webpack"type": "module"package.jsonpostcss.config.jspostcss.config.cjsdev:vinextbuild:vinextvite.config.tsnpm install -D vinext vite @vitejs/plugin-react
# App Router only:
npm install -D @vitejs/plugin-rsc react-server-dom-webpackpackage.json{
"scripts": {
"dev": "vinext dev",
"build": "vinext build",
"start": "vinext start",
"deploy": "vinext deploy"
}
}npx skills add cloudflare/vinext
# Then in your AI tool: "migrate this project to vinext"| Command | Description |
|---|---|
| Start dev server with HMR |
| Production build |
| Local production server for testing |
| Build + deploy to Cloudflare Workers |
| Automated migration from Next.js |
| Scan for compatibility issues before migrating |
| Delegate to eslint or oxlint |
vinext dev -p 3001 -H 0.0.0.0
vinext deploy --preview
vinext deploy --env staging --name my-app
vinext deploy --skip-build --dry-run
vinext deploy --experimental-tpr
vinext init --port 3001 --skip-check --forceapp/pages/next.config.jsvite.config.tsvite.config.tsimport { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { vinext } from 'vinext/vite'
export default defineConfig({
plugins: [
react(),
vinext(),
],
})vite.config.tsimport { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import rsc from '@vitejs/plugin-rsc'
import { vinext } from 'vinext/vite'
export default defineConfig({
plugins: [
react(),
rsc(),
vinext(),
],
})import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { vinext } from 'vinext/vite'
import { cloudflare } from '@cloudflare/vite-plugin'
export default defineConfig({
plugins: [
cloudflare(),
react(),
vinext(),
],
})import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { vinext } from 'vinext/vite'
import nitro from 'vite-plugin-nitro'
export default defineConfig({
plugins: [
react(),
vinext(),
nitro({ preset: 'vercel' }), // or 'netlify', 'aws-amplify', 'deno-deploy', etc.
],
})my-app/
├── app/ # App Router (auto-detected)
│ ├── layout.tsx
│ ├── page.tsx
│ └── api/route.ts
├── pages/ # Pages Router (auto-detected)
│ ├── index.tsx
│ └── api/hello.ts
├── public/ # Static assets
├── next.config.js # Loaded automatically
├── package.json
└── vite.config.ts # Optional for basic usage// pages/index.tsx
import type { GetServerSideProps, InferGetServerSidePropsType } from 'next'
type Props = { data: string }
export const getServerSideProps: GetServerSideProps<Props> = async (ctx) => {
return { props: { data: 'Hello from SSR' } }
}
export default function Home({ data }: InferGetServerSidePropsType<typeof getServerSideProps>) {
return <h1>{data}</h1>
}// pages/posts/[id].tsx
import type { GetStaticPaths, GetStaticProps } from 'next'
export const getStaticPaths: GetStaticPaths = async () => {
return {
paths: [{ params: { id: '1' } }, { params: { id: '2' } }],
fallback: false,
}
}
export const getStaticProps: GetStaticProps = async ({ params }) => {
return { props: { id: params?.id } }
}
export default function Post({ id }: { id: string }) {
return <p>Post {id}</p>
}// pages/api/hello.ts
import type { NextApiRequest, NextApiResponse } from 'next'
export default function handler(req: NextApiRequest, res: NextApiResponse) {
res.status(200).json({ message: 'Hello from vinext' })
}// app/page.tsx
export default async function Page() {
const data = await fetch('https://api.example.com/data').then(r => r.json())
return <main>{data.title}</main>
}// app/api/route.ts
import { NextRequest, NextResponse } from 'next/server'
export async function GET(request: NextRequest) {
return NextResponse.json({ status: 'ok' })
}
export async function POST(request: NextRequest) {
const body = await request.json()
return NextResponse.json({ received: body })
}// app/actions.ts
'use server'
export async function submitForm(formData: FormData) {
const name = formData.get('name')
// server-side logic here
return { success: true, name }
}// app/form.tsx
'use client'
import { submitForm } from './actions'
export function Form() {
return (
<form action={submitForm}>
<input name="name" />
<button type="submit">Submit</button>
</form>
)
}// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const token = request.cookies.get('token')
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url))
}
return NextResponse.next()
}
export const config = {
matcher: ['/dashboard/:path*'],
}// app/api/kv/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { getCloudflareContext } from 'cloudflare:workers'
export async function GET(request: NextRequest) {
const { env } = getCloudflareContext()
const value = await env.MY_KV.get('key')
return NextResponse.json({ value })
}// app/page.tsx
import Image from 'next/image'
export default function Page() {
return (
<Image
src="/hero.png"
alt="Hero"
width={800}
height={400}
priority
/>
)
}// app/nav.tsx
'use client'
import Link from 'next/link'
import { useRouter, usePathname } from 'next/navigation'
export function Nav() {
const router = useRouter()
const pathname = usePathname()
return (
<nav>
<Link href="/">Home</Link>
<Link href="/about">About</Link>
<button onClick={() => router.push('/dashboard')}>Dashboard</button>
</nav>
)
}# Authenticate (once)
wrangler login
# Deploy
vinext deploy
# Deploy to preview
vinext deploy --preview
# Deploy to named environment
vinext deploy --env production --name my-production-appCLOUDFLARE_API_TOKENwrangler loginwrangler.tomlname = "my-app"
compatibility_date = "2024-01-01"
compatibility_flags = ["nodejs_compat"]
[[kv_namespaces]]
binding = "MY_KV"
id = "your-kv-namespace-id"
[[r2_buckets]]
binding = "MY_BUCKET"
bucket_name = "my-bucket"npm install -D vite-plugin-nitro
# Then add nitro plugin to vite.config.ts with your target preset
# nitro({ preset: 'netlify' })
# nitro({ preset: 'vercel' })
# nitro({ preset: 'aws-amplify' })next.config.jsnext.config.js// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
remotePatterns: [
{ protocol: 'https', hostname: 'images.example.com' },
],
},
env: {
MY_VAR: process.env.MY_VAR,
},
redirects: async () => [
{ source: '/old', destination: '/new', permanent: true },
],
rewrites: async () => [
{ source: '/api/:path*', destination: 'https://backend.example.com/:path*' },
],
}
module.exports = nextConfignpx vinext checknext.config.js.env.env.local.env.production# .env.local
NEXT_PUBLIC_API_URL=https://api.example.com
DATABASE_URL=$DATABASE_URL// Accessible in client code (NEXT_PUBLIC_ prefix)
const apiUrl = process.env.NEXT_PUBLIC_API_URL
// Server-only
const dbUrl = process.env.DATABASE_URL// tsconfig.json — works as-is
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
}
}npm install -D tailwindcss postcss autoprefixer
# Rename postcss.config.js → postcss.config.cjs (vinext init does this automatically)// postcss.config.cjs
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}# vinext init handles this automatically, or rename manually:
mv postcss.config.js postcss.config.cjs
mv tailwind.config.js tailwind.config.cjspackage.json"type": "module"vinext initvite.config.tsvinext init --forcevinext init --skip-checkvinext dev -p 3001
vinext init --port 3001wranglerwrangler login
# or set env var:
export CLOUDFLARE_API_TOKEN=your_token_herevinext deploy --dry-runnpm install -D @vitejs/plugin-rsc react-server-dom-webpackvite.config.tsreact()rsc()next/imagenext/linknext/routernext/navigationnext/headnext/fontnext/dynamicnext.config.js