Skip to main content

Virtual Module System

Vinext uses Vite’s virtual module system to generate dynamic entry points for different runtime environments. This guide explains how virtual modules work, how they’re generated, and the resolution patterns used.

What Are Virtual Modules?

Virtual modules are modules that don’t exist on disk. They’re generated at runtime by Vite plugins and resolved via the resolveId and load hooks. Why use virtual modules?
  • Generate entry points based on file-system routing
  • Embed route metadata at build time
  • Create environment-specific entries (RSC vs SSR vs Client)
  • Avoid writing generated files to disk

Virtual Module IDs

Vinext defines several virtual modules:

Pages Router

App Router

The \0 prefix is a Rollup convention indicating a resolved virtual module. It prevents other plugins from attempting to resolve it further.

Resolution Hooks

From packages/vinext/src/index.ts:

Resolution Quirks

Vite has several edge cases in virtual module resolution that must be handled.
1. Build-time root prefix During SSR builds, Vite prefixes virtual module IDs with the project root path:
  • Dev: virtual:vinext-server-entry
  • Build: /home/user/project/virtual:vinext-server-entry
The resolveId hook strips the root prefix before matching. 2. \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 pattern matching:
3. Absolute paths required Virtual modules have no real file location. All imports within them must use absolute paths:
Vinext uses import.meta.url and path resolution to generate absolute paths:

Server Entry Generation

The Pages Router server entry (virtual:vinext-server-entry) is a self-contained SSR handler:
Key features:
  • All page and API routes are imported and registered
  • Config is embedded at build time (no runtime file I/O)
  • Middleware matching logic is inlined
  • ISR cache helpers are included
  • Supports both development and production

RSC Entry Generation

The App Router RSC entry (virtual:vinext-rsc-entry) is the request handler: From packages/vinext/src/server/app-dev-server.ts:
Key features:
  • All routes are discovered and imported at build time
  • Metadata files are embedded as base64 (works on Workers with no filesystem)
  • Full middleware, rewrite, redirect, and header logic is inlined
  • Component tree building logic is included
  • Server action handling is built-in

Client Entry Generation

The Pages Router client entry (virtual:vinext-client-entry) bootstraps React hydration:
The App Router browser entry (virtual:vinext-app-browser-entry) is generated by the RSC plugin and consumes the RSC stream for hydration.

SSR Entry Generation

The App Router SSR entry (virtual:vinext-app-ssr-entry) consumes the RSC stream and renders HTML:
Why separate SSR entry? The RSC and SSR environments have different import conditions:
  • RSC uses react-server (server-only APIs)
  • SSR uses node (React client APIs for SSR)
They must use separate module graphs to avoid mixing incompatible APIs.

Shim Resolution

All next/* imports are resolved to shim modules:
Shims are resolved to absolute paths so they work from any importer location.

Next Steps

Architecture Deep Dive

Core architecture and patterns

RSC Integration

React Server Components integration

Build Pipeline

Production build pipeline

Contributing

Contribute to Vinext