> ## 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.

# Adding Features

> How to add new features to vinext

This guide walks through the process of adding new features to vinext.

## Development Workflow

### Adding a New Feature

When adding a new feature to vinext:

1. **Check if Next.js has it** — look at Next.js source to understand expected behavior
2. **Add tests first** — put test cases in the appropriate `tests/*.test.ts` file
3. **Implement in shims or server** — most features are either a shim (`next/*` module) or server-side logic
4. **Add fixture pages if needed** — `tests/fixtures/` has test apps for integration testing
5. **Run the full test suite** before committing

### Fixing Bugs

**Always check dev and prod server parity.** Request handling logic exists in multiple places that must stay in sync:

* `server/app-dev-server.ts` — App Router dev (generates the RSC entry)
* `server/dev-server.ts` — Pages Router dev
* `server/prod-server.ts` — Pages Router production (handles middleware, routing, SSR directly)
* `cloudflare/worker-entry.ts` — Cloudflare Workers entry

The App Router production server delegates to the built RSC entry, so it inherits fixes from `app-dev-server.ts`. But the Pages Router production server has its own middleware/routing/SSR logic that must be updated separately.

<Warning>
  When fixing a bug in any of these files, check whether the same bug exists in the others. Do not leave known bugs as "follow-ups" — fix them in the same PR.
</Warning>

## Architecture Patterns

Understanding these architectural patterns is critical for implementing features correctly.

### RSC and SSR Are Separate Vite Environments

This is the single most important architectural detail. The RSC environment and the SSR environment are **separate Vite module graphs with separate module instances**. If you set state in a module in the RSC environment (e.g., `setNavigationContext()` in `next/navigation`), the SSR environment's copy of that module is unaffected.

Per-request state (pathname, searchParams, params, headers, cookies) must be **explicitly passed** from the RSC entry to the SSR entry via the `handleSsr(rscStream, navContext)` call. The SSR entry calls the setter before rendering and cleans up afterward.

**Rule of thumb:** Any per-request state that `"use client"` components need during SSR must be passed across the environment boundary. They don't share module state.

### What `@vitejs/plugin-rsc` Does vs What vinext Does

The RSC plugin handles:

* Bundler transforms for `"use client"` / `"use server"` directives
* RSC stream serialization (wraps `react-server-dom-webpack`)
* Multi-environment builds (RSC/SSR/Client)
* CSS code-splitting and auto-injection
* HMR for server components
* Bootstrap script injection for client hydration

vinext handles everything else:

* File-system routing (scanning `app/` and `pages/` directories)
* Request lifecycle (middleware, headers, redirects, rewrites, then route handling)
* Layout nesting and React tree construction
* Client-side navigation and prefetching
* Caching (ISR, `"use cache"`, fetch cache)
* All `next/*` module shims

The RSC entry's `default` export is the request handler. The plugin calls it for every request; vinext does route matching, builds the React tree, renders to RSC stream, and delegates to the SSR entry for HTML.

### Production Builds Require `createBuilder`

You **must** use `createBuilder()` + `builder.buildApp()` for production builds, not `build()` directly. Calling `build()` from the Vite JS API doesn't trigger the RSC plugin's multi-environment build pipeline. `buildApp()` runs the 5-step RSC/SSR/client build sequence in the correct order.

### Virtual Module Resolution Quirks

* **Build-time root prefix:** Vite prefixes virtual module IDs with the project root path when resolving SSR build entries. The `resolveId` hook must handle both `virtual:vinext-server-entry` and `<root>/virtual:vinext-server-entry`.
* **`\0` prefix in client environment:** When the RSC plugin generates its browser entry, it imports virtual modules using the already-resolved `\0`-prefixed ID. Vite's `import-analysis` plugin can't resolve this. Fix: strip the `\0` prefix before matching in `resolveId`.
* **Absolute paths required:** Virtual modules have no real file location, so all imports within them must use absolute paths.

### Next.js 15+ Thenable Params

Next.js 15 changed `params` and `searchParams` to Promises. For backward compatibility with pre-15 code, vinext creates "thenable objects":

```js theme={null}
Object.assign(Promise.resolve(params), params)
```

This works both as `await params` (new style) and `params.id` (old style). The same pattern applies to `generateMetadata` and `generateViewport`.

### ISR Architecture

The ISR cache layer sits **above** `CacheHandler`, not inside it. `CacheHandler` (matching Next.js 16's interface) is a simple key-value store. ISR semantics live in a separate `isr-cache.ts` module:

* **Stale-while-revalidate:** Returns stale entries (not null) while background regeneration runs
* **Dedup:** A `Map<string, Promise>` keyed by cache key ensures only one regeneration per key at a time
* **Revalidate tracking:** A side map stores revalidate durations by cache key (populated on MISS, read on HIT/STALE)
* **Tag invalidation:** Tag-invalidated entries are hard-deleted (return null), unlike time-expired entries which return stale

The caching layer is pluggable via `setCacheHandler()`. KV is the default for Cloudflare Workers. The ISR logic works automatically with any backend.

## Adding Examples

The `examples/` directory contains real-world Next.js apps ported to run on vinext. When adding a new example:

### Adding a New Example

1. Create a directory under `examples/` with a `package.json` (use `"vinext": "workspace:*"`)
2. Add a `vite.config.ts` with `vinext()` and `cloudflare()` plugins
3. Add a `wrangler.jsonc` — for simple apps use `"main": "vinext/server/app-router-entry"` (no custom worker entry needed)
4. Add the example to the deploy matrix in `.github/workflows/deploy-examples.yml`:
   * Add to `matrix.example` array (with `name`, `project`, `wrangler_config`)
   * Add to the `examples` array in the PR comment step
5. Add a smoke test entry in `scripts/smoke-test.sh` — add a line to the `CHECKS` array:
   ```
   "your-example-name  /  expected-text-in-body"
   ```
6. Run `./scripts/smoke-test.sh` locally to verify after deploying

### Porting Strategy

The examples in `.github/repos.json` are the ecosystem of Next.js apps we want to support. When porting one:

1. **Use App Router** unless the original app specifically requires Pages Router
2. **Keep the same content** — the goal is to prove the app works on vinext, not to rewrite it
3. **Use `@mdx-js/rollup`** for MDX support (vinext auto-detects and injects it, or you can register it manually in `vite.config.ts`)
4. **File issues** for anything that requires workarounds — missing shims, unsupported config options, etc.
5. **Don't depend on the original framework's build plugins** — e.g., Nextra's webpack plugin won't work; port the content and build a lightweight equivalent

## Ecosystem Library Compatibility

When adding support for third-party Next.js libraries:

* **`next/navigation.js` (with .js extension):** Libraries like `nuqs` import with the `.js` extension. Vite's `resolve.alias` does exact matching, so a `resolveId` hook strips `.js` from `next/*` imports and redirects through the shim map.
* **next-themes:** Works out of the box. ThemeProvider, `useTheme`, SSR script injection all function correctly.
* **next-intl:** Requires deep integration. It expects a plugin from `next.config.ts` (`createNextIntlPlugin`) that injects config at build time. Simply installing and importing doesn't work.
* **General pattern:** Libraries that only import from `next/*` public APIs tend to work. Libraries that depend on Next.js build plugins or internal APIs need custom shimming.

## Looking at Next.js Source

**When in doubt, look at how Next.js does it.** Vinext aims to replicate Next.js behavior, so their implementation is the authoritative reference.

If you're trying to understand how something works under the hood — route matching, RSC streaming, caching behavior, API semantics — the best approach is to go look at the Next.js source code and understand what they're doing, then apply it to how we do things in this project.
