5.7 · difficulty 4/4 · 12 min read
RunnableDevEnvironment vs FetchableDevEnvironment
The two communication models of the Environment API — in-process direct execution (Runnable) and cross-process HTTP fetching (Fetchable). When should you use each?
Two communication models
The two subclasses of DevEnvironment correspond to two different module execution models:
RunnableDevEnvironment
Modules execute in the same Node.js process as the Vite dev server:
[Node.js process]
├── Vite Dev Server
│ └── DevEnvironment (ssr)
│ └── ModuleRunner
│ └── directly eval / Function-executes module code
│
└── Your SSR server code
└── runner.import("/src/entry.ts") ← same-process callAdvantages:
- Zero communication overhead
- Shared memory (global variables, module state)
- Easy to debug (same Node.js inspector)
Disadvantages:
- Only runs in Node.js (cannot simulate Edge Runtime)
- No true isolation between modules
Best for: standard Node.js SSR, traditional server-side rendering.
FetchableDevEnvironment
Modules execute in a separate process or runtime; the Vite server provides modules via HTTP or IPC:
[Node.js process] [Separate process/runtime]
Vite Dev Server ←→ Your code runs here
DevEnvironment (worker) For example:
provides transform service - workerd (Cloudflare)
accepts module fetch requests - Deno
provides HMR notifications - Another Node.js processAdvantages:
- True runtime isolation (test Cloudflare code in workerd)
- More accurately simulates the production environment
Disadvantages:
- Communication overhead (HTTP/IPC)
- More complex to debug
- More complex to configure
Best for: Cloudflare Workers development, Edge Runtime development, scenarios with high isolation requirements.
Basic usage of RunnableDevEnvironment
Most SSR scenarios use RunnableDevEnvironment, which is also Vite's default behavior:
// Standard SSR configuration, uses RunnableDevEnvironment by default
export default defineConfig({
environments: {
ssr: {
resolve: {
conditions: ["node", "import"],
},
},
},
})
// In server.js:
const runner = getModuleRunner(vite.environments.ssr)
const { render } = await runner.import("/src/entry-server.tsx")
// render() executes in the same Node.js processHow FetchableDevEnvironment works
For FetchableDevEnvironment, module execution happens in another runtime:
1. You start a worker in workerd
2. Import requests from within the worker go to the Vite dev server
3. The Vite server executes resolve + transform
4. The compiled module code is returned to workerd
5. workerd executes the code
6. When a file changes, Vite notifies workerd to refresh via HotChannelThis means FetchableDevEnvironment requires a companion client-side piece running inside the target runtime to receive modules.
Implementing a FetchableDevEnvironment for Cloudflare Workers
import { defineConfig } from "vite"
// Pseudocode: Cloudflare Workers FetchableDevEnvironment configuration
export default defineConfig({
environments: {
worker: {
// Configuration for how to communicate with workerd
// Refer to official docs and the Cloudflare Vite plugin for the exact API
},
},
})In practice, for Cloudflare Workers development, the recommended approach is to use:
@cloudflare/vite-plugin— Cloudflare's official Vite pluginwrangler dev— Cloudflare's CLI tool
These tools implement all the glue code required for FetchableDevEnvironment internally.
How to choose
Does your code need to run in a Node.js environment?
├── Yes → use RunnableDevEnvironment (default)
└── No → you need to simulate a specific runtime:
├── Cloudflare Workers → use the official Cloudflare Vite plugin
├── Deno Deploy → wait for official Deno integration
└── Custom Edge → implement FetchableDevEnvironment (complex)Self-check
- What is the most fundamental difference between RunnableDevEnvironment and FetchableDevEnvironment?
- Why is FetchableDevEnvironment more appropriate than RunnableDevEnvironment for Cloudflare Workers development?
- How is HMR implemented in a FetchableDevEnvironment?
- If your project only does standard Node.js SSR, do you need to care about the distinction between these two Environment types?
// Determine which DevEnvironment model should be used for each scenario below, and explain your reasoning:
// Scenario A: Next.js App Router SSR (running on Node.js)
// Scenario B: Cloudflare Workers API route
// Scenario C: Testing Vercel Edge Middleware locally
// Scenario D: Traditional Express + React isomorphic application
// Scenario E: Node.js SSR application running in Docker
// Scenario F: Service Worker pre-caching strategy testing
// For each scenario, fill in:
// - Recommended Environment model: Runnable or Fetchable
// - Reason: one sentence explanation