Skip to content

HMR causes 100% CPU usage with $derived / dynamic component #17427

@techniq

Description

@techniq

Describe the bug

Sorry I haven't created a more isolated example but encountered this issue and found a workaround today and wanted to submit an issue in case it helps reveal the root cause.

In the new LayerChart docs we load examples into context (based on the current page usage) and then retrieve them (either by relative path or component/name) in an Example component.

We were using let example = $derived.by(...) to retrieve the example and worked great EXCEPT when we would update +layout.svelte or any other component on the page (including Example) and trigger a HMR, it would cause the CPU to spike to 100% and continuously trigger re-renders of the Example component.

Image

Here is relevant code (but see Example for more context)

<script>
  let example = $derived.by(() => {
  	if (path) {
  		// Path-based example
  		const resolvedPath = resolveExamplePath(path, page.url.pathname);
  		return examples.get()?.current['__path__']?.[resolvedPath];
  	} else if (component && name) {
  		// Component/name-based example
  		return examples.get()?.current[component]?.[name];
  	}
  	return undefined;
  });
</script>

{#if example}
  <example.component />
{/if}

Interestingly, if you commented out <example.component /> it would no longer cause the spike.

I tried a multitude of solutions / workarounds ({#key}, {@const Component = example.component}, let Component = $derived(example?.component), remove bind:ref) but the only workaround I found was to replace the $derived.by() with $state and $effect (which is counter to typical recommendations).

// Use $state + $effect to break potential infinite reactivity loops during HMR
<script>
	let example = $state<{ component: any; source: string } | undefined>(undefined);

	$effect(() => {
		if (path) {
			// Path-based example
			const resolvedPath = resolveExamplePath(path, page.url.pathname);
			example = examples.get()?.current['__path__']?.[resolvedPath];
		} else if (component && name) {
			// Component/name-based example
			example = examples.get()?.current[component]?.[name];
		} else {
			example = undefined;
		}
	});
</script>

{#if example}
  <example.component />
{/if}

We haven't tried downgrading Svelte versions after finding this workaround as the project is always gingerly walking between many async/remote function pain points (rolling back to an older version sometimes fix one thing and re-introduces another). Without testing, but my first intuition is it could be caused by #17105.

We know what we signed up for using experimental async/ remote functions 🤣

Reproduction

This is part of a big docs-v2 PR we're working on. You can check out the PR and run pnpm dev.

Logs

System Info

`svelte@5.46.0` with experimental async enabled, `@sveltejs/kit@2.49.2` with experimental remote functions enabled

Severity

blocking an upgrade

Metadata

Metadata

Assignees

No one assigned

    Labels

    awaiting submitterneeds a reproduction, or clarification

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions