Skip to content

Turnkey SSR dev fails with non-runnable Cloudflare ssr environment #281

Closed

Description

@brenelz

Summary

Turnkey SSR development fails when the ssr environment is supplied by @cloudflare/vite-plugin because vite-plugin-solid calls the legacy server.ssrLoadModule() API, which only works with a RunnableDevEnvironment.

Cloudflare recommends assigning its Worker to the framework's SSR environment with viteEnvironment: { name: 'ssr' }. Its environment runs modules in workerd and is intentionally not runnable in the Vite server process.

Production builds and previews work; the failure is specific to development.

Versions

  • vite-plugin-solid: 3.0.0-next.15
  • vite: 8.1.5
  • solid-js: 2.0.0-beta.23
  • @solidjs/web: 2.0.0-beta.23
  • @cloudflare/vite-plugin: 1.46.0
  • wrangler: 4.113.0

Minimal reproduction

vite.config.ts:

import { cloudflare } from '@cloudflare/vite-plugin'
import { defineConfig } from 'vite'
import solid from 'vite-plugin-solid'

export default defineConfig({
  plugins: [
    cloudflare({ viteEnvironment: { name: 'ssr' } }),
    solid({ ssr: {}, serverFunctions: true }),
  ],
})

wrangler.jsonc:

{
  "$schema": "./node_modules/wrangler/config-schema.json",
  "name": "solid-cloudflare",
  "main": "./src/worker.ts",
  "compatibility_date": "2026-07-22",
  "compatibility_flags": ["nodejs_compat"],
  "assets": {
    "directory": "./dist/client",
    "binding": "ASSETS"
  }
}

src/worker.ts:

import { handleRequest } from 'virtual:solid-ssr-handler'

export default {
  fetch(request: Request) {
    return handleRequest(request)
  },
}

Add a basic src/App.tsx, run pnpm dev, and request / with Accept: text/html.

Actual behavior

Internal server error: ssrLoadModule requires the 'ssr' environment to be a runnable environment.
    at ssrLoadModule (.../vite/dist/node/chunks/node.js)
    at Proxy.ssrLoadModule (.../vite/dist/node/chunks/node.js)
    at .../vite-plugin-solid/dist/esm/index.mjs

The call originates from the turnkey middleware in src/ssr/index.ts, which does:

const handler = await server.ssrLoadModule(HANDLER_ID)

The server-functions development middleware in src/server-functions/index.ts also calls server.ssrLoadModule() for both the referenced module and the virtual handler, so it appears to have the same environment limitation.

Expected behavior

Turnkey SSR should either dispatch through the environment's supported communication mechanism or defer to the runtime provider's middleware instead of assuming that ssr is runnable in the Vite server process.

The existing Worker entry already imports the adapter-agnostic virtual:solid-ssr-handler, so Cloudflare can execute the handler inside workerd if Solid's Node-oriented middleware delegates the request.

Verified production behavior

  • pnpm build succeeds and emits the Worker/server and client bundles.
  • pnpm preview returns 200 with server-rendered HTML from the Worker runtime.

Current workaround

Keeping the Cloudflare Worker out of the ssr environment during development leaves Vite's default runnable SSR environment available to Solid, while preserving the production environment merge:

export default defineConfig(({ command }) => ({
  plugins: [
    cloudflare({
      viteEnvironment: command === 'build' ? { name: 'ssr' } : undefined,
    }),
    solid({ ssr: {}, serverFunctions: true }),
  ],
}))

This avoids the exception but means development does not follow the intended single Cloudflare SSR environment setup.

Possible direction

Use Vite's Environment API instead of server.ssrLoadModule():

  • For isRunnableDevEnvironment(environment), load virtual handlers with environment.runner.import().
  • For a FetchableDevEnvironment, dispatch the web request with environment.dispatchFetch().
  • For a provider-owned raw environment such as Cloudflare's current environment, call next() so the provider can route the request to its runtime.
  • Apply equivalent capability handling to the server-functions development middleware and ensure the generated Worker handler can serve server-function requests in development.

An explicit turnkey option such as ssr.devMiddleware: false could also provide an adapter-controlled escape hatch, although automatic capability detection would avoid Cloudflare-specific configuration.

Activity

  1. ryansolid commented on Jul 29, 2026

    @ryansolid
    Member

    Fixed on next by the approach from #286 — thanks @brenelz for both the detailed report and the implementation. Two mechanisms landed:

    • Automatic capability detection. When the dev server's ssr environment isn't runnable — exactly the viteEnvironment: { name: 'ssr' } Cloudflare setup here — the plugin detects it and stands its dev middlewares down (turnkey SSR serving and the server-function endpoint both) instead of calling ssrLoadModule. Requests fall through to the provider, and the generated virtual:solid-ssr-handler self-serves when workerd imports it: dev entry CSS travels through an HMR-tracked virtual styles module and server functions compose into the handler, so handleRequest(request) is the whole dev contract, same as production. Zero config.
    • Explicit ssr.external: true. The umbrella switch for integrations that own the whole server side — it additionally skips turnkey's server-build wiring, and covers providers that own a differently named environment that detection can't see.

    The narrow, endpoint-only knob serverFunctions.devMiddleware: false remains for non-turnkey setups; the README on next now documents how the three options relate.

    Ships in the next 3.0.0-next release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions