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