# Solidjs V2 Migration

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

- Skill: `khmm12/solidjs-v2-migration` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add khmm12/solidjs-v2-migration`
- Raw SKILL.md: https://api.skillmd.com/api/skills/khmm12/solidjs-v2-migration/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: khmm12 (https://skillmd.com/u/khmm12)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/khmm12/solidjs-v2-migration

---


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

1. Dependencies: `solid-js@2.x`, add `@solidjs/web`, and replace
   `babel-preset-solid` with matching `@solidjs/babel-plugin` (or use
   `@solidjs/vite-plugin`).
2. `tsconfig.json`: `"jsxImportSource": "@solidjs/web"`.
3. 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:

1. **Effects**: single-callback `createEffect` → split `(compute, apply)`;
   `on()` → compute phase; `initialValue` → default parameter; `onCleanup`
   inside effects → returned cleanup.
2. **`createComputed`** → `createMemo` / split effect / `createSignal(fn)` —
   pick by intent (derivation / side effect / writable derived).
3. **`batch`** → delete; add `flush()` only where code reads its own writes
   synchronously.
4. **`createResource`** → async `createMemo` (or `createProjection` for keyed
   collections) + `<Loading>`; `.loading`/`.error`/`refetch`/`mutate` each map
   differently — see the table.
5. **Mutations**: ad-hoc flag flipping / `startTransition` → `action()` +
   optimistic primitives + awaitable `refresh()` or `until()` live-source
   acknowledgement.
6. **Stores**: `produce` wrappers → plain drafts; path setters → drafts (or
   `storePath` compat); `reconcile` moves inside the draft; `createMutable` →
   `createStore`.
7. **Lists**: `<Index>` → `<For keyed={false}>`; audit default `<For>`
   callbacks — item is now raw, index is an accessor.
8. **DOM**: `use:` → ref factories; `on:`/`attr:`/`bool:`/`class:`/`style:`
   namespaces → standard forms; `/*@once*/` → reactive or `defaultValue`;
   camelCase attributes → lowercase.
9. **Context**: `.Provider` → context-as-component; delete `useX`-with-throw
   wrappers (`useContext` now returns `T` and throws without Provider).
10. **Server functions**: old `.GET`/`.withOptions` call sites → declaration
   wrappers, `prepareRequest`, or per-call `invoke`; audit module-level versus
   function-level wrapper trust boundaries.
11. **`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.

