solidjs/solid
Debugging
Active contributors: Ryan Carniato, Damian Tarnawski
This page collects techniques for understanding what the runtime is doing — both inside this repo and inside an app that consumes Solid.
Use the dev build
packages/solid/package.json declares a development export condition:
"development": {
"types": "./types/index.d.ts",
"import": "./dist/dev.js",
"require": "./dist/dev.cjs"
}The dev bundle (dist/dev.js) is built by packages/solid/rollup.config.js with replaceDev(true), which inlines _SOLID_DEV_ as true. That toggle (declared in packages/solid/src/reactive/signal.ts as IS_DEV) gates:
- The
DEVhooks object exposed fromsolid-js:export const DEV = IS_DEV ? ({ hooks: DevHooks, writeSignal, registerGraph } as const) : undefined; - Source map bookkeeping (
SourceMapValue, thenameparameter oncreateSignal/createMemo, theOwner.sourceMaparray). - Multi-instance warnings (
globalThis.Solid$$deduplication check at the bottom ofpackages/solid/src/index.ts). - Friendly error messages — most
throw new Error(...)calls in the runtime have a long-form dev message and a short prod string.
In a Vite or Rollup app, set the development export condition (Vite does this automatically for dev servers). If you are exercising the source directly via the Vitest config, IS_DEV is wired through the alias resolution.
DevHooks
packages/solid/src/reactive/signal.ts exports a mutable DevHooks object:
export const DevHooks: {
afterUpdate: (() => void) | null;
afterCreateOwner: ((owner: Owner) => void) | null;
afterCreateSignal: ((signal: SignalState<any>) => void) | null; // @deprecated
afterRegisterGraph: ((sourceMapValue: SourceMapValue) => void) | null;
};Solid's official devtools attach to these hooks at startup. You can do the same in a debugging session:
import { DEV } from 'solid-js';
DEV?.hooks.afterCreateOwner = (owner) => console.log('new owner', owner.name);
DEV?.hooks.afterRegisterGraph = (n) => console.log('graph', n.name, n.value);packages/solid/store/src/store.ts has its own DevHooks.onStoreNodeUpdate that fires when a store property changes.
Naming nodes for diagnostics
Most reactive primitives accept a name option in the dev build:
const [count, setCount] = createSignal(0, { name: 'count' });
const doubled = createMemo(() => count() * 2, undefined, { name: 'doubled' });The name flows into the SourceMapValue and is exposed via DevHooks.afterRegisterGraph to devtools.
Common debugging recipes
"Why isn't my effect re-running?"
The reactive graph wires subscriptions at read time. If the signal is being read inside an untrack(...), inside a non-reactive context, or via destructuring in a way that turns an accessor into a value at module load, the subscription was never created. getListener() (exported from solid-js) returns the active Computation if you are inside a tracking scope and null otherwise — useful for an inline console.log(getListener()?.name) to verify you are tracked.
"Why is my effect running too often?"
Use createMemo to memoize the upstream computation, or pass { equals: ... } to a signal/memo to widen the equality check. equalFn (the default) is Object.is. Setting equals: false forces every set to notify.
"Suspense never resolves"
<Suspense> (in packages/solid/src/render/Suspense.ts) tracks pending resources via a context-supplied increment/decrement pair. Resources increment on read() while pending and decrement on resolution. Common causes:
- A resource is created in a parent of the
<Suspense>boundary and so the boundary never sees the increment. read()is called insideuntrack, breaking subscription.- A resource fetcher rejected without an
onErrorboundary; the error short-circuits resolution.<ErrorBoundary>is your friend.
"SSR output is empty / errors with is not supported in the browser"
The browser bundle of solid-js/web ships server-mock.ts (packages/solid/web/src/server-mock.ts) which logs an error and returns undefined for renderToString, renderToStringAsync, etc. If you see those errors, you are calling SSR APIs from a browser-condition build. Switch the export condition to node/deno/worker (or use the default non-browser entry) to get the real implementation in packages/solid/src/server/.
"Hydration mismatch / duplicated DOM nodes"
The CHANGELOG notes 1.8 made hydration more robust against streamed-in lazy components and top-level fragments. If you still see hydration corruption:
- Confirm the SSR and client builds were produced with the same
babel-preset-solidversion (this is the very first item in the CHANGELOG 1.8 "Other" section: a Babel/runtime version mismatch breaks hydration markers). - Inspect
sharedConfig(packages/solid/src/render/hydration.ts) — itscontext.idandcontext.countshould match between the SSR HTML attributes and the client. - Check for non-deterministic component output (random keys,
Date.now(), etc.) that diverges between server and client.
Debugging the build itself
If a Rollup build is producing an unexpected shape:
cd packages/solid
pnpm build:js # runs `rollup -c` onlypackages/solid/rollup.config.js is the single source of truth for entry points and external mappings. The replaceDev helper at the top of that file is the place to add a new dev-only marker.
Debugging types
pnpm --filter solid-js test-types runs tsc --project tsconfig.test.json against the *.type-tests.ts suite. To narrow in on a single error:
cd packages/solid
npx tsc --project tsconfig.test.json --noEmit | head -50The first error is usually the meaningful one; everything below tends to be cascade failures.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.