All skills
sveltejs avatar

/svelte-core-bestpractices

@1eb06fd official
by Sveltesveltejs/mcp331 stars
40

Guidance on writing fast, robust, modern Svelte code. Load this skill whenever in a Svelte project and asked to write/edit or analyze a Svelte component or module. Covers reactivity, event handling, styling, integration with libraries and more.

Use this Skill: https://skilld.dev/gh/sveltejs/mcp/svelte-core-bestpractices

This session only. Nothing lands on disk.

referenceshydratable.md

≈1k tokens on demand. Your agent reads this file only when SKILL.md points to it.

In Svelte, when you want to render asynchronous content data on the server, you can simply await it. This is great! However, it comes with a pitfall: when hydrating that content on the client, Svelte has to redo the asynchronous work, which blocks hydration for however long it takes:

<script>
  import { getUser } from 'my-database-library';

  // This will get the user on the server, render the user's name into the h1,
  // and then, during hydration on the client, it will get the user _again_,
  // blocking hydration until it's done.
  const user = await getUser();
</script>

<h1>{user.name}</h1>

That's silly, though. If we've already done the hard work of getting the data on the server, we don't want to get it again during hydration on the client. hydratable is a low-level API built to solve this problem. You probably won't need this very often — it will be used behind the scenes by whatever datafetching library you use. For example, it powers remote functions in SvelteKit.

To fix the example above:

<script>
  import { hydratable } from 'svelte';
  import { getUser } from 'my-database-library';

  // During server rendering, this will serialize and stash the result of `getUser`, associating
  // it with the provided key and baking it into the `head` content. During hydration, it will
  // look for the serialized version, returning it instead of running `getUser`. After hydration
  // is done, if it's called again, it'll simply invoke `getUser`.
  const user = await hydratable('user', () => getUser());
</script>

<h1>{user.name}</h1>

This API can also be used to provide access to random or time-based values that are stable between server rendering and hydration. For example, to get a random number that doesn't update on hydration:

import { hydratable } from 'svelte';
const rand = hydratable('random', () => Math.random());

If you're a library author, be sure to prefix the keys of your hydratable values with the name of your library so that your keys don't conflict with other libraries.

Serialization

All data returned from a hydratable function must be serializable. But this doesn't mean you're limited to JSON — Svelte uses devalue, which can serialize all sorts of things including Map, Set, URL, and BigInt. Check the documentation page for a full list. In addition to these, thanks to some Svelte magic, you can also fearlessly use promises:

<script>
  import { hydratable } from 'svelte';
  const promises = hydratable('random', () => {
    return {
      one: Promise.resolve(1),
      two: Promise.resolve(2)
    }
  });
</script>

{await promises.one}
{await promises.two}

CSP

hydratable adds an inline <script> block to the head returned from render. If you're using Content Security Policy (CSP), this script will likely fail to run. You can provide a nonce to render:

const nonce = crypto.randomUUID();

const { head, body } = await render(App, {
	csp: { nonce }
});

This will add the nonce to the script block, on the assumption that you will later add the same nonce to the CSP header of the document that contains it:

response.headers.set(
  'Content-Security-Policy',
  `script-src 'nonce-${nonce}'`
 );

It's essential that a nonce — which, British slang definition aside, means 'number used once' — is only used when dynamically server rendering an individual response.

If instead you are generating static HTML ahead of time, you must use hashes instead:

const { head, body, hashes } = await render(App, {
	csp: { hash: true }
});

hashes.script will be an array of strings like ["sha256-abcd123"]. As with nonce, the hashes should be used in your CSP header:

response.headers.set(
  'Content-Security-Policy',
  `script-src ${hashes.script.map((hash) => `'${hash}'`).join(' ')}`
 );

We recommend using nonce over hash if you can, as hash will interfere with streaming SSR in the future.

Source: SKILL.md on GitHub

No alerts16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    The skill provides legitimate guidance and best practices for Svelte 5 development. It contains only documentation and code examples with no identified security risks.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer6mo

    10 files scanned · No issues

  • ZeroLeaks5mo

    1 finding · Score: 86/100

Signed by skilld at 1eb06fd. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub last week.

Activeupdated 2 months ago
  • svelte
  • reactivity
  • runes
  • components
  • styling
  • state-management
  • event-handling
  • best-practices
  • svelte5

README badge

README badge for sveltejs/mcp/svelte-core-bestpractices

Provides best practices for writing Svelte 5 components using runes (`$state`, `$derived`, `$effect`, `$props`), event handling, snippets, styling, and context. Load this skill in Svelte projects when writing or reviewing components to ensure idiomatic reactivity patterns and avoid legacy features.

Generated from the current SKILL.md.

Should I use $state for all variables?
No. Only use $state for variables that should be reactive and trigger updates in effects, derived values, or templates. Regular variables can stay as normal JavaScript. For large objects that are only reassigned (not mutated), use $state.raw instead to avoid proxy overhead.
When should I use $derived instead of $effect?
$derived should be your first choice for computing values from state. Use $effect only as an escape hatch for side effects like syncing to external libraries. Never use $effect just to update a variable based on state changes.
Can I use $effect to listen to window or document events?
No. Use <svelte:window> and <svelte:document> instead. Avoid $effect or onMount for attaching global event listeners.
What should I use instead of on:click and other legacy event directives?
Use the onclick attribute directly (e.g. <button onclick={() => {...}}>), which works with attribute shorthand and spread props.
Should I destructure items in each blocks if I need to bind to them?
No. Avoid destructuring if you need to mutate the item with something like bind:value={item.count}, as it can break reactivity.

Generated from the current SKILL.md. These answers refresh after source changes.