Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .agents/skills/migrate-to-vinext/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ description: Migrates Next.js projects to vinext (Vite-based Next.js reimplement

vinext reimplements the Next.js API surface on Vite. Existing `app/`, `pages/`, and `next.config.js` work as-is — migration is a package swap, config generation, and ESM conversion. No changes to application code required.

## Starting a New Project?

If you're starting from scratch (not migrating an existing Next.js app), use `create-vinext-app` instead:

```bash
npm create vinext-app@latest
```

This scaffolds a complete vinext project with Cloudflare Workers support. The rest of this skill is for migrating existing Next.js projects.

## FIRST: Verify Next.js Project

Confirm `next` is in `dependencies` or `devDependencies` in `package.json`. If not found, STOP — this skill does not apply.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,6 @@ These features are intentionally excluded:
- `next export` (legacy — use `output: 'export'`)
- Turbopack/webpack configuration
- `next/jest` (use Vitest)
- `create-next-app` scaffolding
- `create-next-app` scaffolding — Replaced by `create-vinext-app` (`npm create vinext-app@latest`)
- Bug-for-bug parity with undocumented Next.js behavior
- Native Node modules in Workers (sharp, resvg, satori — auto-stubbed in production)
57 changes: 57 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,63 @@ jobs:
kill "$SERVER_PID" 2>/dev/null || true
exit 1

create-vinext-app:
name: create-vinext-app (${{ matrix.os }}, ${{ matrix.template }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
template: [app, pages]
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-node-pnpm
- name: Build packages
run: pnpm run build

- name: Pack vinext for local install
run: npm pack --pack-destination "${{ runner.temp }}"
working-directory: packages/vinext

- name: Scaffold a fresh create-vinext-app project
run: node packages/create-vinext-app/dist/index.js "${{ runner.temp }}/cva-test" --template ${{ matrix.template }} --yes --skip-install
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI passes an absolute path as the positional arg.

This feeds ${{ runner.temp }}/cva-test as the project name. The code at index.ts:140 correctly handles path.isAbsolute() by extracting the basename. However, the resulting package.json will have "name": "cva-test" and the worker name will be "cva-test". That's fine and works, but it's worth noting that this CI step is exercising the absolute-path code path, not the typical user flow of create-vinext-app my-app.

Consider adding a second test that uses a plain name (the typical user flow) to ensure both paths are covered in CI.


- name: Scaffold with plain project name
working-directory: ${{ runner.temp }}
run: |
node "${{ github.workspace }}/packages/create-vinext-app/dist/index.js" cva-plain-test --template ${{ matrix.template }} --yes --skip-install
test -f cva-plain-test/package.json

- name: Install vinext from local tarball
working-directory: ${{ runner.temp }}/cva-test
shell: bash
run: npm install "${{ runner.temp }}"/vinext-*.tgz

- name: Install remaining deps
working-directory: ${{ runner.temp }}/cva-test
run: npm install

- name: Start dev server and verify HTTP 200
working-directory: ${{ runner.temp }}/cva-test
shell: bash
run: |
npx vite dev --port 3098 &
SERVER_PID=$!

for i in $(seq 1 30); do
STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3098/ || true)
if [ "$STATUS" = "200" ]; then
echo "Server responded with HTTP 200 (attempt $i)"
kill "$SERVER_PID" 2>/dev/null || true
exit 0
fi
sleep 1
done

echo "Server did not respond with HTTP 200 within 30 seconds"
kill "$SERVER_PID" 2>/dev/null || true
exit 1

e2e:
name: E2E (${{ matrix.project }})
runs-on: ubuntu-latest
Expand Down
14 changes: 12 additions & 2 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,20 @@ jobs:
echo "Publishing vinext@${VERSION} (was ${LATEST})"
echo "previous=${LATEST}" >> "$GITHUB_OUTPUT"

- name: Publish (OIDC trusted publishing)
- name: Bump create-vinext-app version
working-directory: packages/create-vinext-app
run: |
npm version "${{ steps.version.outputs.version }}" --no-git-tag-version
echo "Publishing create-vinext-app@${{ steps.version.outputs.version }}"

- name: Publish vinext (OIDC trusted publishing)
working-directory: packages/vinext
run: npm publish --access public --provenance

- name: Publish create-vinext-app (OIDC trusted publishing)
working-directory: packages/create-vinext-app
run: npm publish --access public --provenance

- name: Tag release
run: |
git tag "v${{ steps.version.outputs.version }}"
Expand Down Expand Up @@ -166,7 +176,7 @@ jobs:
PAYLOAD=$(jq -n \
--arg version "$VERSION" \
--arg summary "$SUMMARY" \
'{"text": ("*vinext v" + $version + " published to npm*\n\n" + $summary + "\n\nnpm: https://www.npmjs.com/package/vinext/v/" + $version)}')
'{"text": ("*vinext v" + $version + " + create-vinext-app published to npm*\n\n" + $summary + "\n\nnpm: https://www.npmjs.com/package/vinext/v/" + $version)}')

echo "::group::Webhook payload"
echo "$PAYLOAD"
Expand Down
6 changes: 5 additions & 1 deletion .oxfmtrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,9 @@
"semi": true,
"singleQuote": false,
"trailingComma": "all",
"ignorePatterns": ["tests/fixtures/ecosystem/**", "examples/**"]
"ignorePatterns": [
"tests/fixtures/ecosystem/**",
"examples/**",
"packages/create-vinext-app/templates/**"
]
}
7 changes: 6 additions & 1 deletion .oxlintrc.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
{
"ignorePatterns": ["fixtures/ecosystem/**", "tests/fixtures/ecosystem/**", "examples/**"]
"ignorePatterns": [
"fixtures/ecosystem/**",
"tests/fixtures/ecosystem/**",
"examples/**",
"packages/create-vinext-app/templates/**"
]
}
44 changes: 27 additions & 17 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ pnpm run lint # oxlint
pnpm run fmt # oxfmt (format)
pnpm run fmt:check # oxfmt (check only, no writes)
pnpm run build # Build the vinext package
pnpm --filter create-vinext-app test # create-vinext-app tests
pnpm --filter create-vinext-app build # Build the CLI
```

### Project Structure
Expand All @@ -39,6 +41,11 @@ packages/vinext/src/
server/ # SSR handlers, ISR, middleware
cloudflare/ # KV cache handler

packages/create-vinext-app/
src/ # CLI source
templates/ # App Router & Pages Router scaffolding templates
tests/ # Unit, integration, e2e tests

tests/
*.test.ts # Vitest tests
fixtures/ # Test apps (pages-basic, app-basic, etc.)
Expand All @@ -49,14 +56,16 @@ examples/ # User-facing demo apps

### Key Files

| File | Purpose |
| -------------------------- | ------------------------------------------------------------------ |
| `index.ts` | Vite plugin — resolves `next/*` imports, generates virtual modules |
| `shims/*.ts` | Reimplementations of `next/link`, `next/navigation`, etc. |
| `server/dev-server.ts` | Pages Router SSR handler |
| `entries/app-rsc-entry.ts` | App Router RSC entry generator |
| `routing/pages-router.ts` | Scans `pages/` directory |
| `routing/app-router.ts` | Scans `app/` directory |
| File | Purpose |
| -------------------------------------------- | ------------------------------------------------------------------ |
| `index.ts` | Vite plugin — resolves `next/*` imports, generates virtual modules |
| `shims/*.ts` | Reimplementations of `next/link`, `next/navigation`, etc. |
| `server/dev-server.ts` | Pages Router SSR handler |
| `entries/app-rsc-entry.ts` | App Router RSC entry generator |
| `routing/pages-router.ts` | Scans `pages/` directory |
| `routing/app-router.ts` | Scans `app/` directory |
| `packages/create-vinext-app/src/index.ts` | CLI entry point for `npm create vinext-app` |
| `packages/create-vinext-app/src/scaffold.ts` | Template copying and variable substitution |

---

Expand Down Expand Up @@ -119,15 +128,16 @@ pnpm test -t "middleware"

**Which test files to run** depends on what you changed:

| If you changed... | Run these tests |
| ---------------------------------------------- | ---------------------------------------------------------------------------------------- |
| A shim (`shims/*.ts`) | `tests/shims.test.ts` + the specific shim test (e.g., `tests/link.test.ts`) |
| Routing (`routing/*.ts`) | `tests/routing.test.ts`, `tests/route-sorting.test.ts` |
| App Router server (`entries/app-rsc-entry.ts`) | `tests/app-router.test.ts`, `tests/features.test.ts` |
| Pages Router server (`server/dev-server.ts`) | `tests/pages-router.test.ts` |
| Caching/ISR | `tests/isr-cache.test.ts`, `tests/fetch-cache.test.ts`, `tests/kv-cache-handler.test.ts` |
| Build/deploy | `tests/deploy.test.ts`, `tests/build-optimization.test.ts` |
| Next.js compat features | `tests/nextjs-compat/` (the relevant file) |
| If you changed... | Run these tests |
| ------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| A shim (`shims/*.ts`) | `tests/shims.test.ts` + the specific shim test (e.g., `tests/link.test.ts`) |
| Routing (`routing/*.ts`) | `tests/routing.test.ts`, `tests/route-sorting.test.ts` |
| App Router server (`entries/app-rsc-entry.ts`) | `tests/app-router.test.ts`, `tests/features.test.ts` |
| Pages Router server (`server/dev-server.ts`) | `tests/pages-router.test.ts` |
| Caching/ISR | `tests/isr-cache.test.ts`, `tests/fetch-cache.test.ts`, `tests/kv-cache-handler.test.ts` |
| Build/deploy | `tests/deploy.test.ts`, `tests/build-optimization.test.ts` |
| Next.js compat features | `tests/nextjs-compat/` (the relevant file) |
| create-vinext-app (`packages/create-vinext-app/`) | `pnpm --filter create-vinext-app test` |

**Let CI run the full suite.** The full `pnpm test` and all 5 Playwright E2E projects run in CI on every PR. You do not need to run the full suite locally before pushing. CI will catch any cross-cutting regressions.

Expand Down
17 changes: 17 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,23 @@ For browser-level debugging (verifying rendered output, client-side navigation,

Check the [open issues](https://github.com/cloudflare/vinext/issues). If you're looking to contribute, those are a good place to start.

### Working on create-vinext-app

The `packages/create-vinext-app/` package is a standalone CLI for scaffolding new vinext projects.

```bash
# Run tests
pnpm --filter create-vinext-app test

# Build
pnpm --filter create-vinext-app build

# Test manually
node packages/create-vinext-app/dist/index.js test-app --yes --skip-install
```

Templates live in `packages/create-vinext-app/templates/`. When updating dependency versions in templates, update both `app-router/package.json.tmpl` and `pages-router/package.json.tmpl`.

## Project structure

See `AGENTS.md` for the full project structure, key files, architecture patterns, and development workflow.
17 changes: 13 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,20 @@ Options: `-p / --port <port>`, `-H / --hostname <host>`, `--turbopack` (accepted

`vinext init` options: `--port <port>` (default: 3001), `--skip-check`, `--force`.

### Starting a new vinext project
### New Project

Run `npm create next-app@latest` to create a new Next.js project, and then follow these instructions to migrate it to vinext.
```bash
npm create vinext-app@latest
```

This scaffolds a new vinext project with Cloudflare Workers support. You'll be prompted for a project name and router type (App Router or Pages Router).

Options:

In the future, we will have a proper `npm create vinext` new project workflow.
- `--template <app|pages>` — Router template (default: app)
- `--yes` / `-y` — Skip prompts
- `--skip-install` — Skip dependency installation
- `--no-git` — Skip git init

### Migrating an existing Next.js project

Expand Down Expand Up @@ -528,7 +537,7 @@ These are intentional exclusions:
- **`next export` (legacy)** — Use `output: 'export'` in config instead.
- **Turbopack/webpack configuration** — This runs on Vite. Use Vite plugins instead of webpack loaders/plugins.
- **`next/jest`** — Use Vitest.
- **`create-next-app` scaffolding** — Not a goal.
- **`create-next-app` scaffolding** — Replaced by `create-vinext-app` (`npm create vinext-app@latest`).
- **Bug-for-bug parity with undocumented behavior** — If it's not in the Next.js docs, we probably don't replicate it.

## Known limitations
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "vinext-monorepo",
"private": true,
"scripts": {
"build": "pnpm --filter vinext run build",
"build": "pnpm --filter vinext --filter create-vinext-app run build",
"test": "vitest run",
"test:watch": "vitest",
"generate:google-fonts": "node scripts/generate-google-fonts.js",
Expand Down
39 changes: 39 additions & 0 deletions packages/create-vinext-app/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"name": "create-vinext-app",
"version": "0.0.1",
"description": "Scaffold a new vinext project targeting Cloudflare Workers",
"keywords": [
"cloudflare",
"create",
"next",
"scaffold",
"vinext",
"vite",
"workers"
],
"license": "MIT",
"bin": {
"create-vinext-app": "dist/index.js"
},
"files": [
"dist",
"templates"
],
"type": "module",
"scripts": {
"build": "tsc",
"pretest": "tsc",
"test": "vitest run",
"test:watch": "vitest"
},
"dependencies": {
"@clack/prompts": "^0.10.0"
},
"devDependencies": {
"typescript": "^5.8.2",
"vitest": "^3.2.1"
},
"engines": {
"node": ">=18"
}
}
Loading
Loading