agentsclimarketplace

Solid js 2.x migration

Skill doeixd/solid-skill/solid-js-2.x-migration

Agent skills for SolidJS 1.x, Solid 2.x migration and API guidance, and SolidStart v2 workflows.

Install
npx -y skills add doeixd/solid-skill --skill solid-js-2.x-migration

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 7 stars7 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Guidance for migrating an existing SolidJS 1.x codebase to SolidJS 2.x beta or next, including identifying old APIs, rewriting code to the new semantics, sequencing changes safely, and validating the migration with warnings and tests. Use this whenever the user asks to upgrade, port, convert, or migrate SolidJS code from 1.x to 2.x, especially when they mention renamed imports, removed APIs like Index or createResource, new effect behavior, Loading or Errored, store API changes, failing tests after upgrade, or ecosystem breakage during the move to Solid 2.x beta or next.

SKILL.md

19.7 KB, ~4.5k tokens by cl100k_base, as published. Nobody here has run it

SolidJS 2.x Migration

Use this skill when the user already has Solid 1.x code and wants to move it to Solid 2.x beta or next.

This skill is migration-oriented. Its job is not just to explain the new APIs, but to convert old code safely and preserve behavior where possible.

If the user only wants a narrow explanation of a Solid 2.x primitive or warning and is not actively upgrading existing code, prefer the Solid 2.x API skill instead.

Goals

  • Identify likely 1.x patterns quickly.
  • Apply the highest-confidence migrations first.
  • Avoid shallow renames that miss semantic changes.
  • Use tests and dev warnings to validate the migration.
  • Be honest about beta-era ecosystem gaps and package workarounds.

Scope boundary

  • Use this skill for upgrades, ports, conversions, staged migration plans, and debugging breakage that appeared during or after the upgrade.
  • Keep the focus on moving existing code safely, not on giving a general tour of the 2.x API surface.
  • When a task is primarily Start-specific, use the SolidStart skill alongside this one rather than assuming the framework boundary from generic Solid guidance.

Migration mindset

Treat the migration as two kinds of work:

  • mechanical changes that are mostly straightforward
  • semantic changes that need careful reasoning and validation

Do not present everything as equally safe. Call out where a rewrite is mechanical versus where behavior may change.

Do not invent replacement APIs that are not part of the Solid 2 migration guidance in this skill. If a candidate rewrite depends on neighboring framework helpers or uncertain beta exports, say that explicitly and keep the migration on the highest-confidence Solid core path.

How to use this skill

Read this skill in passes instead of treating every section as equally important.

  1. Start with Migration mindset, Recommended migration workflow, and High-confidence mechanical migrations.
  2. Use Canonical before and after rewrites for the first implementation pass.
  3. Use Semantics that need careful migration when behavior, warnings, tests, or lifetime issues are involved.
  4. Use Task-focused migration guidance and Common migration mistakes to catch for review, triage, and planning.

Recommended migration workflow

  1. Inspect package versions, imports, and the current usage of 1.x APIs.
  2. Make import and package-boundary changes.
  3. Migrate removed or renamed APIs.
  4. Migrate behavior-sensitive reactivity patterns.
  5. Run tests or at least read dev warnings carefully.
  6. Fix remaining ecosystem or tooling issues.

High-confidence mechanical migrations

Apply these first when present:

  • solid-js/web -> @solidjs/web
  • solid-js/store imports -> solid-js
  • solid-js/h -> @solidjs/h
  • solid-js/html -> @solidjs/html
  • solid-js/universal -> @solidjs/universal
  • Suspense -> Loading
  • ErrorBoundary -> Errored
  • Index -> For keyed={false}
  • createSelector -> createProjection or function-form createStore
  • unwrap -> snapshot
  • mergeProps -> merge
  • splitProps -> omit
  • classList -> class
  • use: directives -> ref directive factories
  • attr: and bool: -> standard attribute behavior
  • oncapture: removed
  • onMount -> onSettled

Even for mechanical migrations, quickly verify surrounding semantics instead of blindly rewriting.

Also remember the non-trivial removals:

  • createResource is not a simple rename; migrate toward async computations plus Loading
  • startTransition and useTransition go away; express pending and transition UX with built-in transitions, isPending, Loading, and optimistic primitives
  • createComputed is removed; replace it based on intent rather than by pattern-matching syntax

Canonical before and after rewrites

Keep rewrites terse and behavior-aware.

Imports

// 1.x
import { render } from "solid-js/web";
import { createStore } from "solid-js/store";

// 2.x beta
import { render } from "@solidjs/web";
import { createStore } from "solid-js";

Index to For

// 1.x
<Index each={items()}>{(item, i) => <Row item={item()} index={i} />}</Index>

// 2.x beta
<For each={items()} keyed={false}>{(item, i) => <Row item={item()} index={i()} />}</For>

The migration is not just renaming the component. The index is now also an accessor.

Suspense to Loading

// 1.x
<Suspense fallback={<Spinner />}>
  <Page />
</Suspense>

// 2.x beta
<Loading fallback={<Spinner />}>
  <Page />
</Loading>

ErrorBoundary to Errored

// 1.x
<ErrorBoundary fallback={(err, reset) => <Fallback err={err} reset={reset} />}>
  <Page />
</ErrorBoundary>

// 2.x beta
<Errored fallback={(err, reset) => <Fallback err={err} reset={reset} />}>
  <Page />
</Errored>

onMount to onSettled

// 1.x
onMount(() => {
  measureLayout();
});

// 2.x beta
onSettled(() => {
  measureLayout();
});

Treat this as a lifecycle semantic review, not a blind search-and-replace, especially when old mount logic created nested primitives or assumed immediate post-setter reads.

Single-callback effect to split effect

// 1.x
createEffect(() => {
  document.title = title();
});

// 2.x beta
createEffect(
  () => title(),
  value => {
    document.title = value;
  }
);

Cleanup moves to apply side

// 1.x
createEffect(() => {
  const id = setInterval(() => console.log(name()), 1000);
  onCleanup(() => clearInterval(id));
});

// 2.x beta
createEffect(
  () => name(),
  value => {
    const id = setInterval(() => console.log(value), 1000);
    return () => clearInterval(id);
  }
);

Top-level prop read fix

// 1.x style that now warns
function Title(props) {
  const t = props.title;
  return <h1>{t}</h1>;
}

// 2.x beta
function Title(props) {
  return <h1>{props.title}</h1>;
}

Store helper changes

// 1.x
const plain = unwrap(store);
const merged = mergeProps(defaults, overrides);
const [local, rest] = splitProps(props, ["class"]);

// 2.x beta
const plain = snapshot(store);
const merged = merge(defaults, overrides);
const rest = omit(props, "class");

Important semantic change:

  • with merge, undefined is an explicit override value, not a skipped key

createSelector to projection

// 1.x
const isSelected = createSelector(selectedId);

// 2.x beta
const selected = createProjection((draft) => {
  const id = selectedId();
  draft[id] = true;
  if (draft._prev != null) delete draft[draft._prev];
  draft._prev = id;
}, {});

If the old code used selector-like patterns, think in terms of projection-oriented derived state rather than hunting for a one-to-one selector replacement.

Draft-first store update

// 1.x style
setStore("user", "address", "city", "Paris");

// 2.x preferred
setStore(s => {
  s.user.address.city = "Paris";
});

// 2.x compatibility bridge
setStore(storePath("user", "address", "city", "Paris"));

Setter return values also matter now:

setStore(s => {
  return { ...s, list: [] };
});

That is a top-level shallow replacement or diff, not the same thing as mutating the draft in place.

Semantics that need careful migration

Batching and test behavior

In 2.x, reads do not immediately observe just-written signal values. If tests or imperative code read state immediately after setters, they may now need a flush point.

Use flush() narrowly, especially in tests or imperative DOM code. Do not scatter it everywhere as a generic fix.

const [count, setCount] = createSignal(0);
setCount(1);
flush();
expect(count()).toBe(1);

The practical insight from early migrations: this tends to show up in unit tests far more than end-to-end tests.

For imperative DOM reads, flush() is also the replacement for many old batch(...) expectations.

createEffect

Migrate from single-callback effects to compute/apply form when the effect depends on reactive reads and performs side effects.

When migrating:

  • move tracked reads into the compute half
  • move side effects into the apply half
  • return cleanup from the apply half

If the old effect was really deriving state, replace it with a memo or derived primitive instead of translating it directly.

This is one of the biggest migration traps. Do not reward old effect-heavy code by preserving the shape if the real issue is that the code should be declarative.

onMount to onSettled

Replace onMount with onSettled, but verify intent. Some code assumed old lifecycle timing or mixed mount work with nested primitive creation. Treat these cases carefully.

Context.Provider to context-as-provider

// 1.x
<ThemeContext.Provider value="dark">
  <Page />
</ThemeContext.Provider>

// 2.x beta
<ThemeContext value="dark">
  <Page />
</ThemeContext>

createComputed replacement

Choose the replacement by intent:

  • side effects -> split createEffect
  • derived writable state -> function-form createSignal or createStore
  • readonly derivation -> createMemo
// 1.x
createComputed(() => {
  setValue(props.input);
});

// 2.x beta, writable derived state
const [value, setValue] = createSignal(() => props.input);

If the old createComputed was really doing side effects, migrate to split createEffect instead.

Ownership and root lifetime

In 2.x, roots created under an owner are owned by that parent by default. If old code depended on effectively detached lifetime, make the detachment explicit.

const singleton = runWithOwner(null, () => {
  const [value, setValue] = createSignal(0);
  return { value, setValue };
});

Top-level reads and prop destructuring

2.x warns on top-level reactive reads. Migration often requires:

  • moving reads into JSX, memos, or effect compute functions
  • avoiding destructuring reactive props at component top level
  • using untrack only when a one-time read is truly intentional

This also applies to control-flow callback bodies. If old code read reactive values in Show or For callback bodies before returning JSX, move those reads into JSX expressions or a proper reactive scope.

Writes in owned scope

Old patterns that wrote state from memos or tracked scopes should usually be redesigned rather than patched. Prefer derived state or event-driven writes.

pureWrite: true is for narrow internal cases, not for silencing application-state warnings during migration.

Async data

Do not just rename Suspense to Loading and stop there. If the app relied on createResource, migrate toward async computations plus Loading, pending indicators, and explicit refresh patterns.

Also distinguish initial loading from refresh state:

  • Loading for not-ready-yet
  • isPending for updating-again
  • latest when stale-while-refreshing output should remain visible

Reasonable 2.x migration targets include:

  • createMemo(async () => ...) for async derived values
  • createStore(fn, initial) or createProjection(...) for async derived collections or keyed reconciled results

For selector-heavy or keyed-list code, createProjection(...) is often the more faithful migration target.

// 1.x
const [user] = createResource(id, fetchUser);

// 2.x beta
const user = createMemo(async () => {
  const currentId = id();
  return fetchUser(currentId);
});

Then move loading UX to structure:

<Loading fallback={<Spinner />}>
  <Profile user={user()} />
</Loading>

Do not recreate resource.loading flags mechanically. If the old UI wanted stale-while-refreshing behavior, model that with isPending(() => expr) and optionally latest(...).

const listPending = () => isPending(() => users() || posts());

If the old app used startTransition or useTransition for mutation UX, move that logic toward action(...), optimistic primitives, and refresh(...).

const [todos] = createStore(() => api.getTodos(), []);
const [optimisticTodos, setOptimisticTodos] = createOptimisticStore(() => todos(), []);

const addTodo = action(function* (todo) {
  setOptimisticTodos(list => {
    list.push(todo);
  });
  yield api.addTodo(todo);
  refresh(todos);
});

Stores

Prefer draft-first store updates in 2.x. If path-style setters are deeply woven into the codebase, storePath(...) can help stage the migration without rewriting every update immediately.

Use storePath(...) as a bridge, not as the main style to promote once the migration settles.

Ecosystem and tooling caveats

Solid 2.x beta or next migrations may require temporary compatibility work, especially around:

  • testing libraries
  • package aliases
  • SSR bundling
  • deduping local Solid versions
  • package versions pinned to next

If a dependency has not caught up, say so explicitly and separate framework migration work from ecosystem breakage.

Useful migration-era insights from real projects:

  • some packages may still require aliasing or temporary config surgery
  • test tooling may need solid-js/web aliased to @solidjs/web
  • linked local packages may need dedupe settings to avoid multiple Solid copies
  • next tags and temporary overrides may be the practical path during beta adoption
  • development warnings are often your first accurate signal that a migration is semantically wrong, not just syntactically incomplete
  • deep observation is no longer something to stumble into casually; if the old code expected "watch everything" behavior, it may need an explicit deep(store) read
  • loading and mutation state are less primitive-specific now, so many old .loading and transition-wrapper patterns should collapse into Loading, isPending, actions, and optimistic primitives
  • old code that accidentally relied on unowned lifetime may start behaving more sanely in 2.x, but singleton-style integrations should now detach explicitly with runWithOwner(null, ...)
  • writes-under-scope and strict-read warnings are usually design feedback, not noise to suppress

Task-focused migration guidance

Planning a staged migration

Load these ideas first:

  • separate mechanical changes from semantic rewrites
  • change imports and package boundaries early
  • use compatibility bridges such as storePath(...) only when they reduce migration risk
  • leave time for warning cleanup and test fixes, not just code rewrites

Migrating component code

Look for these first:

  • Index to For keyed={false} with accessor-aware callbacks
  • top-level prop reads and destructuring that now warn
  • onMount assumptions that need onSettled
  • Context.Provider habits
  • use: directives that need ref factories

Migrating reactivity patterns

Look for these first:

  • state derivation done through effects instead of createMemo
  • writes to signals or stores from memos or other tracked scopes
  • code that assumes post-setter reads are immediately updated
  • nested createRoot(...) calls that assumed detached lifetime

Migrating async data and mutations

Load these ideas first:

  • do not stop at Suspense -> Loading
  • move async reads toward computations plus Loading
  • treat Loading and isPending as different jobs
  • move old transition-style mutation UX toward action(...), optimistic primitives, and refresh(...)
  • keep async reads and async writes as separate migration concerns

Debugging test and tooling breakage

Look for these first:

  • stale immediate reads after setters in tests
  • aliasing or package-boundary mismatches
  • duplicate Solid versions in linked packages or monorepos
  • ecosystem lag in test libraries or SSR tooling
  • warnings that reveal semantic migration errors rather than bad syntax

Common migration mistakes to catch

MistakeBetter direction
Treating every migration as a rename passSeparate mechanical changes from semantic rewrites and say which is which.
Renaming Suspense to Loading but leaving the old async model intactMigrate the read model too, not just the boundary component.
Treating createResource like it has a one-word replacementMove toward async computations plus Loading, isPending, and refresh(...) where appropriate.
Preserving write-back effects just because they existed in 1.xReplace derivation with createMemo or another derived form when that was the real intent.
Scattering flush() everywhere after test failuresUse it narrowly where a settled point is genuinely needed.
Treating storePath(...) as the final 2.x styleUse it as a bridge, then prefer draft-first setters in migrated code.
Using pureWrite: true to suppress owned-scope warnings in app codeRedesign the state flow instead; reserve pureWrite: true for narrow internal cases.
Forgetting accessor semantics when replacing IndexIn For keyed={false} callbacks, read item() and i() when accessors are provided.
Assuming nested createRoot(...) stayed detached like beforeMake detached lifetime explicit with runWithOwner(null, ...) when it is truly required.
Reaching for createAsync, router helpers, or Start helpers during core migrationStay on the highest-confidence Solid core migration path unless the repo proves a framework-specific target.
Treating warnings as noise to suppressUse them as migration feedback about state flow, read placement, and ownership.

When to pause and ask the user

Ask for confirmation when:

  • a change appears behavior-affecting rather than mechanical
  • a dependency is not Solid 2.x compatible yet
  • there are multiple plausible migration strategies with real tradeoffs

Otherwise, keep moving through the migration.

Output expectations

When migrating code, prefer this structure:

  1. List the 1.x patterns found.
  2. Separate mechanical changes from semantic rewrites.
  3. Apply minimal safe code changes.
  4. Note any remaining warnings, test failures, or ecosystem blockers.

When possible, include one-line reasoning beside each migration hotspot so the user understands why the rewrite happened.

For code rewrites, keep these guardrails:

  • do not switch to unrelated helpers like createAsync or mount unless the local repo proves they are the right target
  • prefer the highest-confidence Solid core migration path from this skill over neighboring framework patterns
  • if the exact beta export is uncertain, say so explicitly instead of fabricating a confident rewrite

Example prompts this skill should handle well

  • "Upgrade this Solid 1 app to Solid 2 beta."
  • "Can you migrate these components away from createResource, Suspense, and Index?"
  • "My tests started failing after moving to Solid 2 next."
  • "What is the safest order to migrate this Solid codebase to 2.x?"

Additional resources

What ships with it: 1 file

4.8 KB alongside SKILL.md

evals/

Keep looking

Skills are one crate of 327,069. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.