Migrate Solid 1.x → 2.0
Convert 1.x code to 2.0 in passes: mechanical first, then semantic, then
diagnostics-driven cleanup. The full rename/removal table with before/after
recipes is in references/migration-map.md — read it before starting; this
file is the workflow.
Step 0 — establish the direction
- Source must be Solid 1.x (imports like
solid-js/web, solid-js/store,
createResource, Suspense). Target version: whatever solid-js@2.x /
@solidjs/web prerelease the project declares (or the latest, if you're also
bumping package.json).
- Prereleases drift. The installed typings (
node_modules/solid-js/types,
@solidjs/web) outrank docs and this skill's references when they disagree.
- Upgrade
solid-js, @solidjs/web, and the Solid compiler integration
together. babel-preset-solid is replaced by @solidjs/babel-plugin; Vite
uses @solidjs/vite-plugin (native @solidjs/compiler by default).
Pass 1 — mechanical (grep-and-replace, low judgement)
- Dependencies:
solid-js@2.x, add @solidjs/web, and replace
babel-preset-solid with matching @solidjs/babel-plugin (or use
@solidjs/vite-plugin).
tsconfig.json: "jsxImportSource": "@solidjs/web".
- Import paths and pure renames — tables at the top of
references/migration-map.md. Greppable: solid-js/web, solid-js/store,
Suspense, SuspenseList, ErrorBoundary, mergeProps, splitProps,
unwrap, onMount, createSelector, Context.Provider, classList,
equalFn, getListener.
Mind the non-1:1 renames: Errored's fallback gets an error accessor
(err()), splitProps → omit inverts the result (rest-only), merge treats
undefined as an override, onSettled is a leaf owner.
Pass 2 — semantic rewrites (per call site, by intent)
Work through references/migration-map.md sections in this order — each names
the decision to make:
- Effects: single-callback
createEffect → split (compute, apply);
on() → compute phase; initialValue → default parameter; onCleanup
inside effects → returned cleanup.
createComputed → createMemo / split effect / createSignal(fn) —
pick by intent (derivation / side effect / writable derived).
batch → delete; add flush() only where code reads its own writes
synchronously.
createResource → async createMemo (or createProjection for keyed
collections) + <Loading>; .loading/.error/refetch/mutate each map
differently — see the table.
- Mutations: ad-hoc flag flipping /
startTransition → action() +
optimistic primitives + awaitable refresh() or until() live-source
acknowledgement.
- Stores:
produce wrappers → plain drafts; path setters → drafts (or
storePath compat); reconcile moves inside the draft; createMutable →
createStore.
- Lists:
<Index> → <For keyed={false}>; audit default <For>
callbacks — item is now raw, index is an accessor.
- DOM:
use: → ref factories; on:/attr:/bool:/class:/style:
namespaces → standard forms; /*@once*/ → reactive or defaultValue;
camelCase attributes → lowercase.
- Context:
.Provider → context-as-component; delete useX-with-throw
wrappers (useContext now returns T and throws without Provider).
- Server functions: old
.GET/.withOptions call sites → declaration
wrappers, prepareRequest, or per-call invoke; audit module-level versus
function-level wrapper trust boundaries.
from/observable → async iterables / push-out effects.
Pass 3 — run dev and fix diagnostics
2.0 ships structured dev diagnostics; the first dev run after migration is the
real review. Typical wave, in order of volume:
STRICT_READ_UNTRACKED — top-level/destructured prop reads the old code
tolerated. Move reads into JSX/memos, or untrack deliberate one-shots.
REACTIVE_WRITE_IN_OWNED_SCOPE (throws) — 1.x effects that write signals.
Rewrite as derivations or move writes to handlers/actions. untrack() is not
an exemption: it suppresses dependency tracking, not ownership.
ASYNC_OUTSIDE_LOADING_BOUNDARY — async reads with no <Loading> ancestor;
add boundaries where fallback UI is wanted.
CLEANUP_IN_FORBIDDEN_SCOPE — onCleanup inside onSettled; return the
cleanup instead.
Then run the test suite: assertions reading right after writes need flush(),
and reactive setups in tests need createRoot.
Pass 4 — behavioral audit (no grep pattern)
The "Behavioral changes that need an audit" section of the map: synchronous
read-after-write assumptions, undefined-as-override in merges, forever-roots
needing runWithOwner(null, ...).
Failure modes
- A 1.x API has no entry in the map → check the installed typings before
inventing a replacement; some conveniences (e.g.
observable,
createDeferred) intentionally have none — surface the gap rather than
papering over it.
- App mounts blank after migration → pending async outside
Loading
defers the root mount (ASYNC_OUTSIDE_LOADING_BOUNDARY in console).
- Migration of one file pulls in half the app → migrate bottom-up (leaf
components first), keep passes 1–2 per-file but expect pass 3 diagnostics to
surface cross-file issues.
1---2name: solidjs-v2-migration3description: Migrate a Solid 1.x codebase, file, or component to SolidJS 2.0 (solid-js 2.x / next / RC). Use when converting code that imports solid-js/web, solid-js/store, createResource, Suspense, onMount, batch, or other 1.x APIs to the 2.0 equivalents. Not for writing new v2 code from scratch (see solidjs-v2).4---56# Migrate Solid 1.x → 2.078Convert 1.x code to 2.0 in passes: mechanical first, then semantic, then9diagnostics-driven cleanup. The full rename/removal table with before/after10recipes is in `references/migration-map.md` — read it before starting; this11file is the workflow.1213## Step 0 — establish the direction1415- Source must be Solid 1.x (imports like `solid-js/web`, `solid-js/store`,16 `createResource`, `Suspense`). Target version: whatever `solid-js@2.x` /17 `@solidjs/web` prerelease the project declares (or the latest, if you're also18 bumping `package.json`).19- Prereleases drift. The installed typings (`node_modules/solid-js/types`,20 `@solidjs/web`) outrank docs and this skill's references when they disagree.21- Upgrade `solid-js`, `@solidjs/web`, and the Solid compiler integration22 together. `babel-preset-solid` is replaced by `@solidjs/babel-plugin`; Vite23 uses `@solidjs/vite-plugin` (native `@solidjs/compiler` by default).2425## Pass 1 — mechanical (grep-and-replace, low judgement)26271. Dependencies: `solid-js@2.x`, add `@solidjs/web`, and replace28 `babel-preset-solid` with matching `@solidjs/babel-plugin` (or use29 `@solidjs/vite-plugin`).302. `tsconfig.json`: `"jsxImportSource": "@solidjs/web"`.313. Import paths and pure renames — tables at the top of32 `references/migration-map.md`. Greppable: `solid-js/web`, `solid-js/store`,33 `Suspense`, `SuspenseList`, `ErrorBoundary`, `mergeProps`, `splitProps`,34 `unwrap`, `onMount`, `createSelector`, `Context.Provider`, `classList`,35 `equalFn`, `getListener`.3637Mind the non-1:1 renames: `Errored`'s fallback gets an error **accessor**38(`err()`), `splitProps` → `omit` inverts the result (rest-only), `merge` treats39`undefined` as an override, `onSettled` is a leaf owner.4041## Pass 2 — semantic rewrites (per call site, by intent)4243Work through `references/migration-map.md` sections in this order — each names44the decision to make:45461. **Effects**: single-callback `createEffect` → split `(compute, apply)`;47 `on()` → compute phase; `initialValue` → default parameter; `onCleanup`48 inside effects → returned cleanup.492. **`createComputed`** → `createMemo` / split effect / `createSignal(fn)` —50 pick by intent (derivation / side effect / writable derived).513. **`batch`** → delete; add `flush()` only where code reads its own writes52 synchronously.534. **`createResource`** → async `createMemo` (or `createProjection` for keyed54 collections) + `<Loading>`; `.loading`/`.error`/`refetch`/`mutate` each map55 differently — see the table.565. **Mutations**: ad-hoc flag flipping / `startTransition` → `action()` +57 optimistic primitives + awaitable `refresh()` or `until()` live-source58 acknowledgement.596. **Stores**: `produce` wrappers → plain drafts; path setters → drafts (or60 `storePath` compat); `reconcile` moves inside the draft; `createMutable` →61 `createStore`.627. **Lists**: `<Index>` → `<For keyed={false}>`; audit default `<For>`63 callbacks — item is now raw, index is an accessor.648. **DOM**: `use:` → ref factories; `on:`/`attr:`/`bool:`/`class:`/`style:`65 namespaces → standard forms; `/*@once*/` → reactive or `defaultValue`;66 camelCase attributes → lowercase.679. **Context**: `.Provider` → context-as-component; delete `useX`-with-throw68 wrappers (`useContext` now returns `T` and throws without Provider).6910. **Server functions**: old `.GET`/`.withOptions` call sites → declaration70 wrappers, `prepareRequest`, or per-call `invoke`; audit module-level versus71 function-level wrapper trust boundaries.7211. **`from`/`observable`** → async iterables / push-out effects.7374## Pass 3 — run dev and fix diagnostics75762.0 ships structured dev diagnostics; the first dev run after migration is the77real review. Typical wave, in order of volume:7879- `STRICT_READ_UNTRACKED` — top-level/destructured prop reads the old code80 tolerated. Move reads into JSX/memos, or `untrack` deliberate one-shots.81- `REACTIVE_WRITE_IN_OWNED_SCOPE` (throws) — 1.x effects that write signals.82 Rewrite as derivations or move writes to handlers/actions. `untrack()` is not83 an exemption: it suppresses dependency tracking, not ownership.84- `ASYNC_OUTSIDE_LOADING_BOUNDARY` — async reads with no `<Loading>` ancestor;85 add boundaries where fallback UI is wanted.86- `CLEANUP_IN_FORBIDDEN_SCOPE` — `onCleanup` inside `onSettled`; return the87 cleanup instead.8889Then run the test suite: assertions reading right after writes need `flush()`,90and reactive setups in tests need `createRoot`.9192## Pass 4 — behavioral audit (no grep pattern)9394The "Behavioral changes that need an audit" section of the map: synchronous95read-after-write assumptions, `undefined`-as-override in merges, forever-roots96needing `runWithOwner(null, ...)`.9798## Failure modes99100- **A 1.x API has no entry in the map** → check the installed typings before101 inventing a replacement; some conveniences (e.g. `observable`,102 `createDeferred`) intentionally have none — surface the gap rather than103 papering over it.104- **App mounts blank after migration** → pending async outside `Loading`105 defers the root mount (`ASYNC_OUTSIDE_LOADING_BOUNDARY` in console).106- **Migration of one file pulls in half the app** → migrate bottom-up (leaf107 components first), keep passes 1–2 per-file but expect pass 3 diagnostics to108 surface cross-file issues.