Skip to content

Syntax error in a lazy route module hangs the SSR stream and shows no overlay#2260

Description

@birkskyum

Duplicates

  • I have searched the existing issues

Latest version

  • I have tested the latest version

Current behavior 馃槸

A syntax error in a file route, or in anything a route imports, makes the page go blank in dev with no error overlay and no console output. The HTTP request never completes: the document shell is flushed and the connection then stays open indefinitely, so the browser considers the page "loaded" and Vite never shows its overlay.

Reproduced on apps/fixtures/basic at 2.0.0-rc.5 by breaking a JSX closing tag in src/components/Counter.tsx:

$ curl -s --max-time 10 http://localhost:4321/ -o /tmp/out.html -w "%{http_code} %{size_download}\n"
200 17154
exit=28          # timed out, stream never terminated

Cause is upstream, not in Start: solidjs/solid#2945. Solid's server-side lazy() attaches only a success handler to the module promise, so a rejected dynamic import leaves the Suspense boundary's _loading flag set forever, notifySuspense never fires, and renderToStream never calls end(). Every file route in Start goes through lazy() (packages/start/src/router.tsx), which is why any route with a syntax error hits it.

Note this is the lazy sibling of #1648, which covers the eager app.tsx path. Those behave differently: an error in app.tsx is an eager import that fails at handler level and does produce an error page, whereas a lazy route module wedges the stream with no output at all.

Expected behavior 馃

The error reaches the nearest ErrorBoundary, the dev overlay shows the syntax error and source frame, and the response completes.

Steps to reproduce 馃暪

  1. pnpm create solid (or use apps/fixtures/basic), start the dev server.
  2. Introduce a syntax error in any route under src/routes/, or in a component the route imports.
  3. Load the route.
  4. The page is blank, no overlay appears, and the request never finishes.

Context 馃敠

Start can work around this locally: its server-only withAssets wrapper in packages/start/src/shared/lazy.ts already wraps the route import for asset collection, so it can catch the rejection there and resolve with a component that throws during render. The throw then reaches the nearest ErrorBoundary and the dev overlay displays it. Once solidjs/solid#2945 lands, that catch becomes redundant and can be dropped.

Filing this to track the upstream dependency. Three related gaps a local workaround does not address:

  1. Client hydration path. Solid's browser lazy uses the same success-only .then in its hydration branch (dist/solid.js:1442); on rejection sharedConfig.count is never decremented, so a chunk that 404s during initial hydration can still stall. Not reproduced, noted in [solid v1] renderToStream() never closes when a lazy() component's module promise rejects聽solid#2945. SPA mode is unaffected, since it goes through createResource.
  2. No auto-reload after the file is fixed. With the page already open, correcting the syntax error does not trigger a reload; a manual refresh is needed.
  3. Wrong error on HMR without a reload. The toolbar reports TypeError: Failed to fetch dynamically imported module rather than the underlying syntax error. Reloading surfaces the real one.

Your environment 馃寧

  • @solidjs/start 2.0.0-rc.5
  • solid-js 1.9.14
  • vite 8.1.5
  • Node v26.3.0

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions