Skip to content

CORRECT006 · Orphan $effect

Severity: critical · Category: correctness

Flags $effect / $effect.pre calls that are guaranteed to run outside component initialisation, so they throw Svelte’s effect_orphan error at runtime:

  • A top-level effect in a .svelte.ts / .svelte.js runes module or in a .svelte <script module> block — it runs when the module is imported, outside any component’s initialisation.
  • A module-scope new of a class declared in the same file whose constructor creates a bare $effect (one not wrapped in $effect.root) — the shared-state-manager pattern. The finding points at the new site.

Not flagged: effects inside functions (including factory functions and IIFEs), effects inside an $effect.root(...) callback, classes that are only instantiated inside components, classes imported from another file, effects in class field initializers or static blocks, and effects in anonymous class expressions (const Store = class { … }). Detection never crosses a function boundary, so it has no false positives by construction — at the cost of missing cross-file and factory variants.

A conditionally-guarded effect — behind a top-level if, or behind a constructor-argument check (constructor(persist) { if (persist) $effect(...) }) — is still flagged even if the guard is never true at runtime, because the guard can’t be evaluated statically. Use an inline suppression (svelte-vitals-disable-next-line CORRECT006) if the guard is intentional.

The Svelte compiler compiles all of these patterns without a warning; the failure is runtime-only. In development it can go unnoticed (the module may only be imported on certain routes), and in production it surfaces as a crash — typically a 500 on every page that imports the module. Reactive effects can only be created while a component is initialising, or inside an explicit $effect.root scope.

store.svelte.ts
class QuizStateManager {
bookmarks = $state<string[]>([]);
constructor() {
// ❌ effect_orphan at runtime — no component context at module scope
$effect(() => {
saveToStorage(this.bookmarks);
});
}
}
export const quizState = new QuizStateManager();

Either create a standalone reactive scope with $effect.root — fine when the effect should live for the whole app; own the returned cleanup function if it shouldn’t:

constructor() {
$effect.root(() => {
$effect(() => {
saveToStorage(this.bookmarks);
});
});
}

Or set the effect up during component initialisation instead:

class QuizStateManager {
bookmarks = $state<string[]>([]);
startPersisting() {
$effect(() => {
saveToStorage(this.bookmarks);
});
}
}
export const quizState = new QuizStateManager();
+layout.svelte
<script>
import { quizState } from '$lib/store.svelte.js';
quizState.startPersisting();
</script>