diff --git a/.agents/skills/migrate-to-vinext/SKILL.md b/.agents/skills/migrate-to-vinext/SKILL.md index 5a921e39..d44d01e7 100644 --- a/.agents/skills/migrate-to-vinext/SKILL.md +++ b/.agents/skills/migrate-to-vinext/SKILL.md @@ -1,6 +1,6 @@ --- name: migrate-to-vinext -description: Migrates Next.js projects to vinext (Vite-based Next.js reimplementation). Load when asked to migrate, convert, or switch from Next.js to vinext. Handles compatibility scanning, package replacement, Vite config generation, ESM conversion, and deployment setup (Cloudflare Workers natively, other platforms via Nitro). +description: Migrates Next.js projects to vinext (Vite-based Next.js reimplementation for Cloudflare Workers). Load when asked to migrate, convert, or switch from Next.js to vinext. Handles compatibility scanning, package replacement, Vite config generation, ESM conversion, and Cloudflare deployment setup. --- # Migrate Next.js to vinext @@ -110,65 +110,12 @@ export default defineConfig({ plugins: [vinext()] }); vinext auto-registers `@vitejs/plugin-rsc` for App Router when the `rsc` option is not explicitly `false`. No manual RSC plugin config needed for local development. -## Phase 4: Deployment (Optional) +## Phase 4: Cloudflare Deployment (Optional) -### Option A: Cloudflare Workers (recommended for Cloudflare) - -If the user wants to deploy to Cloudflare Workers, use `vinext deploy`. It auto-generates `wrangler.jsonc`, worker entry, and Vite config if missing, installs `@cloudflare/vite-plugin` and `wrangler`, then builds and deploys. +If the user wants to deploy to Cloudflare Workers, the simplest path is `vinext deploy` — it auto-generates `wrangler.jsonc`, worker entry, and Vite config if missing, installs `@cloudflare/vite-plugin` and `wrangler`, then builds and deploys. For manual setup or custom worker entries, see [references/config-examples.md](references/config-examples.md). -#### Cloudflare Bindings (D1, R2, KV, AI, etc.) - -To access Cloudflare bindings (D1, R2, KV, AI, Queues, Durable Objects, etc.), use `import { env } from "cloudflare:workers"` in any server component, route handler, or server action: - -```tsx -import { env } from "cloudflare:workers"; - -export default async function Page() { - const result = await env.DB.prepare("SELECT * FROM posts").all(); - return
{JSON.stringify(result)}
; -} -``` - -This works because `@cloudflare/vite-plugin` runs server environments in workerd, where `cloudflare:workers` is a native module. No custom worker entry, no `getPlatformProxy()`, no special configuration needed. Just import and use. - -Bindings must be defined in `wrangler.jsonc`. For TypeScript types, run `wrangler types`. - -**IMPORTANT:** Do not use `getPlatformProxy()`, `getRequestContext()`, or custom worker entries with `fetch(request, env)` to access bindings. These are older patterns. `cloudflare:workers` is the recommended approach and works out of the box with vinext. - -### Option B: Other platforms (via Nitro) - -For deploying to Vercel, Netlify, AWS, Deno Deploy, or any other [Nitro-supported platform](https://v3.nitro.build/deploy), add the Nitro Vite plugin: - -```bash -npm install nitro -``` - -```ts -// vite.config.ts -import { defineConfig } from "vite"; -import vinext from "vinext"; -import { nitro } from "nitro/vite"; - -export default defineConfig({ - plugins: [vinext(), nitro()], -}); -``` - -Build and deploy: - -```bash -NITRO_PRESET=vercel npx vite build # Vercel -NITRO_PRESET=netlify npx vite build # Netlify -NITRO_PRESET=deno_deploy npx vite build # Deno Deploy -NITRO_PRESET=node npx vite build # Node.js server -``` - -Nitro auto-detects the platform in most CI/CD environments, so the preset is often unnecessary. - -**Note:** For Cloudflare Workers, Nitro works but the native integration (`vinext deploy` / `@cloudflare/vite-plugin`) is recommended for the best developer experience with `cloudflare:workers` bindings, KV caching, and one-command deploys. - ## Phase 5: Verify 1. Run `vinext dev` to start the development server @@ -190,6 +137,23 @@ See [references/troubleshooting.md](references/troubleshooting.md) for common mi | `runtime` / `preferredRegion` | Route segment configs ignored | | PPR (Partial Prerendering) | Use `"use cache"` directive instead (Next.js 16 approach) | +## Field Learnings (From Real Migrations) + +1. Auth/session behavior can regress even when pages appear to load. + - In request-wide auth guards (`proxy.ts` / middleware equivalents), treat vinext static asset paths (`/assets/*`) as internal like `/_next/*`. Redirecting these to sign-in breaks anonymous/public rendering. + - When calling auth APIs server-side, do not rely only on `headers()`. Merge cookie-store cookies into the request headers so RSC/server-action paths consistently see session cookies. + - For any DB-backed auth stack, use persistent/shared storage (not in-memory) and ensure required schema exists before first request. Keep stack-specific details in troubleshooting docs. +2. Avoid server actions for high-frequency client mutations. + - If a UI action creates many records (for example AI parsing into many day/category entries), use one route handler (`/api/...`) batch request instead of looped server actions. + - Reason: server actions trigger RSC replay and layout re-execution; with auth-protected layouts this can surface `NEXT_REDIRECT` noise and UX churn. This pattern also applies to Next.js generally, but often becomes more visible during migration. +3. Be careful with `optimizeDeps.exclude`. + - Excluding broad component libraries can cause ESM/CJS runtime mismatches (for example `react/jsx-runtime` named export errors in dev). + - Prefer default optimization unless there is a proven incompatibility. +4. Route segment `error.tsx` can expose shim/runtime gaps. + - If adding segment error boundaries triggers runtime import errors from vinext shims, remove that segment `error.tsx` and rely on the nearest parent or root error boundary until upstream fix is available. +5. Some dev warnings are plugin/internal noise. + - Example: ` must have a valid as value` can be emitted by current RSC tooling and may be non-blocking. + ## Anti-patterns - **Do not modify `app/`, `pages/`, or application code.** vinext shims all `next/*` imports — no import rewrites needed. @@ -197,5 +161,3 @@ See [references/troubleshooting.md](references/troubleshooting.md) for common mi - **Do not copy webpack/Turbopack config** into Vite config. Use Vite-native plugins instead. - **Do not skip the compatibility check.** Run `vinext check` before migration to surface issues early. - **Do not remove `next.config.js`** unless replacing it with `next.config.ts` or `.mjs`. vinext reads it for redirects, rewrites, headers, basePath, i18n, images, and env config. -- **Do not use `getPlatformProxy()` or custom worker entries for bindings.** Use `import { env } from "cloudflare:workers"` instead. This is the modern pattern and works out of the box with vinext and `@cloudflare/vite-plugin`. -- **For Cloudflare Workers, prefer the native integration over Nitro.** `vinext deploy` / `@cloudflare/vite-plugin` provides the best experience with `cloudflare:workers` bindings, KV caching, and image optimization. Nitro works for Cloudflare but the native setup is recommended. diff --git a/.agents/skills/migrate-to-vinext/references/config-examples.md b/.agents/skills/migrate-to-vinext/references/config-examples.md index 90f52573..a5d80496 100644 --- a/.agents/skills/migrate-to-vinext/references/config-examples.md +++ b/.agents/skills/migrate-to-vinext/references/config-examples.md @@ -109,82 +109,61 @@ For custom worker entries (e.g., adding KV cache, image optimization bindings): } ``` -## Accessing Cloudflare Bindings - -Use `import { env } from "cloudflare:workers"` in server components, route handlers, and server actions. No custom worker entry needed. +## VinextOptions -```tsx -// app/page.tsx (server component) -import { env } from "cloudflare:workers"; +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `appDir` | `string` | project root | Custom base directory for `app/` and `pages/` | +| `rsc` | `boolean` | `true` | Auto-register `@vitejs/plugin-rsc` for App Router | -export default async function Page() { - const result = await env.DB.prepare("SELECT * FROM posts").all(); - return
{JSON.stringify(result)}
; -} -``` +## Known-Safe Migration Checklist (App Router) -```ts -// app/api/data/route.ts (route handler) -import { env } from "cloudflare:workers"; +Use this checklist to avoid common real-world regressions during vinext migration. -export async function GET() { - const value = await env.CACHE.get("key"); - return Response.json({ value }); -} -``` +### 1) Auth guard must bypass vinext assets -Define bindings in `wrangler.jsonc`: +If you use request-wide auth (`proxy.ts`), treat `/assets/*` as internal/static just like `/_next/*`. -```jsonc -{ - "name": "my-app", - "compatibility_date": "2026-02-12", - "compatibility_flags": ["nodejs_compat"], - "main": "vinext/server/app-router-entry", - "d1_databases": [{ "binding": "DB", "database_name": "my-db", "database_id": "..." }], - "kv_namespaces": [{ "binding": "CACHE", "id": "..." }], - "r2_buckets": [{ "binding": "BUCKET", "bucket_name": "my-bucket" }], - "ai": { "binding": "AI" } +```ts +function isInternalRequest(request: Request): boolean { + const pathname = new URL(request.url).pathname; + if (pathname.startsWith("/_next/")) return true; + if (pathname.startsWith("/assets/")) return true; // vinext static assets + if (request.headers.has("rsc")) return true; + if (request.headers.has("next-action")) return true; + return false; } ``` -Run `wrangler types` to generate TypeScript types for the `env` object. +### 2) Prefer API route handlers for high-volume client mutations -Do NOT use `getPlatformProxy()`, `getRequestContext()`, or custom worker entries with `fetch(request, env)`. These are older patterns. `cloudflare:workers` is the recommended approach. +Avoid looped server actions for workloads like “AI parsed N entries”. -## App Router — Other Platforms (via Nitro) - -For deploying to Vercel, Netlify, AWS, Deno Deploy, or any other Nitro-supported platform: +Use: +- one client request to `/api/.../batch` +- server-side validation/auth in the route +- one batch write/update flow ```ts -import { defineConfig } from "vite"; -import vinext from "vinext"; -import { nitro } from "nitro/vite"; - -export default defineConfig({ - plugins: [vinext(), nitro()], +// client +await fetch("/api/dashboard/work-blocks/batch", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ entries }), }); ``` -Build with a preset: +### 3) Keep dependency optimization defaults unless proven otherwise -```bash -NITRO_PRESET=vercel npx vite build -NITRO_PRESET=netlify npx vite build -NITRO_PRESET=deno_deploy npx vite build -NITRO_PRESET=node npx vite build -``` +Do not broadly add libraries to `optimizeDeps.exclude` unless you have a verified reason. +This can trigger ESM/CJS runtime mismatches in dev. -Nitro auto-detects the platform in most CI/CD environments, so the `NITRO_PRESET` is often unnecessary. +### 4) Better Auth + SIWE should use persistent/shared DB -**For Cloudflare Workers,** Nitro works but the native integration (`vinext deploy` / `@cloudflare/vite-plugin`) is recommended for the best experience with `cloudflare:workers` bindings, KV caching, and one-command deploys. - -## VinextOptions - -| Option | Type | Default | Description | -|--------|------|---------|-------------| -| `appDir` | `string` | project root | Custom base directory for `app/` and `pages/` | -| `rsc` | `boolean` | `true` | Auto-register `@vitejs/plugin-rsc` for App Router | +For SIWE nonce verification reliability: +- use shared DB-backed auth storage +- ensure Better Auth tables exist (`verification` in particular) +- run explicit bootstrap migration scripts rather than relying on per-request introspection ## vinext deploy flags diff --git a/.agents/skills/migrate-to-vinext/references/troubleshooting.md b/.agents/skills/migrate-to-vinext/references/troubleshooting.md index d25705eb..6f1b217f 100644 --- a/.agents/skills/migrate-to-vinext/references/troubleshooting.md +++ b/.agents/skills/migrate-to-vinext/references/troubleshooting.md @@ -11,6 +11,9 @@ | `vinext: command not found` | vinext not installed or not in PATH | Install vinext: `npm install vinext`, then run via `npx vinext` or package.json scripts | | RSC environment crash on dev start | Native Node module (sharp, satori) loaded in RSC env | vinext auto-stubs these in production; in dev, ensure these are only imported in server code behind dynamic `import()` | | `ASSETS binding not found` | wrangler.jsonc missing assets config | Add `"assets": { "not_found_handling": "none" }` to wrangler.jsonc | +| `NEXT_REDIRECT` during/after server action | Mutation implemented as server action triggers RSC replay + auth-protected layout re-exec | Move high-volume mutations to route handlers (`/api/...`) and batch writes in one request | +| `The requested module '/node_modules/react/jsx-runtime.js' does not provide an export named 'jsx'` | Dependency optimization/shim mismatch (often after `optimizeDeps.exclude` changes) | Remove broad `optimizeDeps.exclude` entries and restart dev with fresh Vite cache | +| Public pages lose CSS/JS when logged out | Request guard redirects vinext assets | Treat `/assets/*` as internal/static in auth guard matcher and bypass auth redirects | ## ESM Conversion Issues @@ -34,6 +37,15 @@ Alternatively, convert these files to ESM (`export default` syntax) and keep the **Cause:** These are Pages Router APIs. They only work in `pages/`, not `app/`. **Fix:** This is expected Next.js behavior, not a vinext issue. +## Auth + Session Stability (App Router) + +- If using `proxy.ts` auth gating, include vinext assets (`/assets/*`) in the internal/public bypass list. +- In server-side auth helpers, merge cookie-store cookies into request headers before calling auth session APIs. +- For Better Auth with SIWE: + - use persistent/shared DB storage + - ensure schema bootstrap includes Better Auth tables (especially `verification`) + - avoid running migration introspection on every request; run explicit bootstrap migrations instead + ## Cloudflare Deployment Issues **Symptom:** Build succeeds but deploy fails with worker size errors.