These posts are aggregated from Svelte GitHub Repository.
This is a long document; get yourself a cup of tea.
You can now use the await
keyword in Svelte — in your <script>
, inside $derived
expressions, and in your markup — by installing the async
branch...
npm i https://pkg.pr.new/svelte@async
...and adding the experimental.async
option to your svelte.config.js
(or wherever you configure Svelte):
// svelte.config.js
export default {
compilerOptions: {
experimental: {
async: true
}
},
kit: {
// ...
}
};
You can also try things out in the async playground.
You will find bugs! This is in no way production-ready. The PR that accompanies this discussion is at https://github.com/sveltejs/svelte/pull/15844.
In olden times, we did asynchronous work like fetching data inside onMount
or an {#await ...}
block. This works but it's quite verbose, and offers no coordination — if two components are both fetching stuff, then they will likely have independently managed error/loading states, often resulting in a janky UI with multiple spinners.
Frameworks like SvelteKit (or Remix or pre-App-Router Next.js, etc) offer an alternative: instead of fetching inside the component, we do our asynchronous work outside the component, for example in load
functions. This typically results in better user experience (we can server-render the data, preload it, and coordinate everything) but it too has problems — prop-drilling, type shenanigans, coarse-grained invalidation, and logic that's hard to delete because it's often not obvious whether something in your load
function is even being used.
In recent years component frameworks have explored a third way: putting asynchronous work back inside components, but in a way that is coordinated:
<Suspense>
, startTransition
, useTransition
, use
and React Server Components, that together allow you to manage async updatescreateResource
API that can be used with <Suspense>
and startTransition
/useTransition
for the same purposeawait
inside a <script setup>
in a component inside a <Suspense>
boundary, though this experimental API only works for component creation, not subsequent updatesWe believe Svelte's compiler-centric nature offers us a way to have component-level async work with significantly better ergonomics and fewer drawbacks than existing approaches.
await
keyword. Avoid framework-specific APIs and idioms to the extent possible<script>
, inside $derived
expressions, in the template, in attributes and component props, in control flow like {#if ...}
blocks, or wherever else)await
expressions need not result in sequential async work. Similarly, asynchronous work in two sibling components (for example) should happen simultaneouslyfoo
changes, and await bar(foo)
is (or will be) visible on the page, any occurrences of foo
in the UI should not update until bar(foo)
resolvesWe also want this to have minimal impact on performance and memory for existing codebases, and to cause little or no breakage for existing apps.
In a nutshell: you can now use await
in three places that will today result in a syntax error:
<script>
$derived
expressionWe also introduce a pending
snippet to <svelte:boundary>
, which allows you to provide UI when await
expressions inside the boundary are first resolving. For now, await
expressions must be inside a boundary with a pending snippet (however deeply nested — for example you might have a single boundary at the very root of your app), though this constraint will likely be relaxed in future.
If state is read inside a $derived
or template expression with an await
, changes to that state will not be reflected in the UI until the expression resolves. For example in a situation like this...
<h1>Weather forecast for {city}</h1>
<p>{await getWeatherForecast(city)}</p>
...the <h1>
will not update until the <p>
does. (If getWeatherForecast
were to fail, it would activate the nearest error boundary.)
To know if an update is currently pending, you can use $effect.pending()
:
{#if $effect.pending()}
<p>loading new data...</p>
{/if}
If unrelated state changes while an update is pending, it will be visible immediately (assuming it doesn't separately cause asynchronous updates).
The most obvious use case is loading data (which could be as simple as fetch
, but in many cases will likely involve yet-to-be-designed utilities for client-server communication), but others include:
<img alt="..." src={await preload('...')}>
)As much as framework authors like to witter on about rendering performance, the thing that really slows apps down is latency. In some cases you can reduce latency by speeding up your back end, or prefetching content you expect to need, or serving content from somewhere close to the user, but those things aren't always possible. What you can do is mitigate the effect of latency by minimising the number of round trips between the browser and the server.
Svelte helps by doing as much work as possible in parallel. For example here...
<li>apples {await getPrice('apple')} each</li>
<li>bananas {await getPrice('banana')} each</li>
<li>canteloupes {await getPrice('canteloupe')} each</li>
...the three calls to getPrice
will happen simultaneously, even though they appear in sequence. Similarly, the three wise monkeys will do all their async work together:
<WiseMonkey verb="see" />
<WiseMonkey verb="hear" />
<WiseMonkey verb="speak" />
Not all work can be parallelized. Any await
expressions inside the <script>
run before expressions in the template, and in the sequence you would expect...
// `a` will be calculated and _then_ `b` will be calculated
let a = $derived(await foo(x));
let b = $derived(await bar(x));
...though note that if x
subsequently updates, a
and b
will be recomputed in parallel. You can of course avoid the initial waterfall like so:
let aPromise = $derived(foo(x));
let bPromise = $derived(bar(x));
let a = $derived(await aPromise);
let b = $derived(await bPromise);
While unnecessary waterfalls can generally be prevented by pushing async work as far into the 'leaves' of your application as possible, there are also necessary waterfalls to contend with. For example, you can't use Promise.all
here, because you need to know artist.id
before you can call getTrackListing
:
let artist = $derived(await search(query));
let tracks = $derived(await getTrackListing(artist.id));
It's better if you can do both fetches on the server, near your database. There are two ways to solve this: either anticipate the need for the track listing when you do the search...
let [artist, tracks] = $derived(await searchAndGetTrackListing(query));
...or use a mechanism like React Server Components, where the rendering happens on the server. Option 1 has less-than-ideal ergonomics, though it's basically equivalent to SvelteKit's load
function. Option 2 is optimal from a waterfall prevention perspective, but we feel RSCs have significant trade-offs.
We anticipate that opinionated data-fetching patterns will emerge over time, along with new approaches inspired by RSCs, to solve this problem.
The work presented so far is the first step of a multi-stage process. While it's useful in its current form, more value will be unlocked with later stages:
Today, server-side rendering is fully synchronous. Because of this, if a <svelte:boundary>
with a pending
snippet is encountered during SSR, the pending
snippet will be rendered.
It would be better to render the content instead, but this requires that SSR become an asynchronous operation.
In an ideal world we would also be able to stream the result where appropriate. This is complicated by the fact that you ideally need to know what's in the <head>
before you get to the <body>
. We have some ideas for how to design this sensibly, but it's not a current priority.
Once we get to this part, it's likely that the requirement for await
expressions to be contained in a <svelte:boundary>
will go away.
In SvelteKit we preload the code and data necessary for a navigation before it even occurs: when you hover over a link, or tap it, we import any modules needed by the route and begin running whichever load
functions need to run. (This behaviour is configurable, of course.)
To make this work in a world where asynchronous work happens inside components, we need to be able to pretend that a state change occurred, and do the resulting updates 'off-screen'. The result can either be applied or discarded.
Colloquially, we've been describing this as a 'fork'. You could imagine several unresolved forks coexisting simultaneously until one reality is chosen (by the user clicking on one of several links they've been close to interacting with, for example). Most likely, we'll add an API to Svelte that allows frameworks like SvelteKit (but also particularly ambitious application authors) to enter the multiverse.
This all sounds very complicated, but in reality it's not all that different to how asynchronous updates already work. Nevertheless, it will take some finagling to get right, and in the interests of shipping we haven't included this work in the current PR.
Unless we want to create server endpoints for everything we want to fetch
inside a component, we'll need tools for interacting with the server. Those tools will need to consider security, HTTP caching, mutations and invalidation, optimistic UI, batching, streaming, custom serialization, type safety and lots of other things.
We're at the early stages of figuring out what this all looks like, but we're very confident that the foundation we're laying here will allow us to design something really good.
Anticipating everyone's reaction: no, we're not going to suddenly make you rewrite all your SvelteKit apps. But taking all the other stuff together, a picture starts to emerge: a version of SvelteKit that's a thinner layer on top of Svelte. For example, as proud as we are of our zero-effort type safety, it's something you no longer need if you're fetching data directly inside your component.
Meanwhile the framework internals could potentially get simpler, because we'd be able to rely on common primitives inside Svelte itself. (Today, we can't reuse that much code between the server and client portions of SvelteKit, and we rely a lot on virtual modules and generated code. Some of that is for historical reasons, but some is necessary because anything asynchronous has to happen outside Svelte.)
It seems likely that a lot of interesting new possibilities will open up as a result of this work, and it seems at least plausible that we'll collectively find ourselves drifting away from primitives like load
. Exactly what this would look like is to be determined, and it's something that we'll figure out together as a community.
startTransition
/useTransition
If you've used React or Solid's suspense implementation, you will have encountered startTransition
and useTransition
. These functions allows you to update state in such a way that the nearest suspense boundary doesn't show its fallback while waiting for any async work that is downstream of the state change.
In Svelte, we don't do this. Instead, state changes that result in asynchronous work always have their effects deferred until the asynchronous work is complete. While that work is ongoing, $effect.pending()
is true
. Any pending
snippets are only shown when a boundary is first being created.
This does create the possibility that something distantly connected to a given piece of state has the power to delay (or prevent!) changes to that state from being reflected in the UI. We think this is preferable to the alternative (in which updates outside a useTransition
or similar cause unwanted fallback UI to appear), though it may be necessary to develop techniques for identifying these chains.
In a case like this...
<p>{a} + {b} = {await fetch(`/add/${a}/${b}`).then((r) => r.json())}</p>
...it's possible to imagine a scenario in which a change to a
is followed by a change to b
while the first fetch
is ongoing. If the second fetch
somehow finishes first, we don't want the first fetch
to be applied afterwards, since the resulting UI would show stale values.
As such, a given async expression must only resolve after its previous versions have also resolved.
Normally in Svelte, $derived
expressions use 'push-pull' reactivity — they are invalidated when their dependencies change (the 'push') but are not re-evaluated until something reads them (the 'pull'). This doesn't work for async deriveds, because if we waited until the value was read the resulting promise would never resolve in time.
Instead, an 'async derived' is really just an effect and a source signal in a trenchcoat. We evalute the expression in the effect, and when the promise resolves, set the source value (unless the effect fired again in the interim).
For the most part, you don't need to think about this. There is one important implication though — while it's possible to create an 'unowned derived' (in other words, a derived that is not part of the 'effect tree', such as one created in an event handler) it is not possible to create an unowned async derived, because effects can only be created inside other effects. (Don't worry if you didn't follow this — it's inside baseball stuff, and describes an edge case you're unlikely to encounter. Svelte will tell you if you get this wrong.)
When a reaction (i.e. an effect or derived) runs, we track its dependencies by seeing which values are read during its execution. In pseudo-code:
let dependencies = null;
function get(source) {
dependencies?.add(source);
return source.value;
}
function update_reaction(reaction) {
dependencies = new Set();
reaction.fn();
reaction.dependencies = dependencies;
dependencies = null;
}
(This is simplified and wrong, but you get the idea.)
For asynchronous functions, this presents a challenge. To understand why, consider the order in which things happen here:
function a() {
console.log('a 1');
b();
console.log('a 2');
}
async function b() {
console.log('b 1');
await 0;
console.log('b 2');
}
a();
This will log a 1
, b 1
, a 2
, b 2
— in other words despite the fact that b
isn't even awaiting an asynchronous value, b 2
isn't logged until after it has returned to a
.
In Svelte terms, this would mean that $derived(await a + b)
or an equivalent {await a + b}
template expression would register a dependency on a
but not on b
. But since we're a compiler we have a trick up our sleeves: we wrap the await
expression in a function that restores the effect context, so that b
is treated as a dependency.
On one level this is spooky compiler magic. But on another, it's just making the system work how a reasonable person would expect it to work. It does mean that if you extract the logic out into a function — {await a_plus_b()}
— it will be treated differently, but during development Svelte will catch those cases and help you fix them.
$derived.by
While you can do this...
let x = $derived.by(async () => await y);
...x
will then be a Promise
, not the value of await y
. There's no sensible place to put the await
that would result in x
not being a Promise
. (This is why it's pretty great that we're a compiler and can make the expression form the default, even though it makes other framework authors mad.)
If you have sufficiently complex logic that you need to use a function block, just declare the function and call it:
async function add(p1: Promise<number>, p2: Promise<number>) {
return await p1 + await p2;
}
let sum = $derived(await add(p1, p2));
Note the aforementioned caveat around context preservation — make sure you pass state into the function, rather than having the function close over the state it references, so that dependencies are correctly attached.
Normally, state does not update in the UI until everything that depends on it has finished resolving. This does not apply to a focused <input>
element, which in many cases will be the source of the state change in question — instead, the rest of the UI 'catches up' to the input.
Because of how async reactivity works, there is one small and unavoidable breaking change: beforeUpdate
and $effect.pre
callbacks no longer run before control flow blocks are updated. If you're using those callbacks to set state that control flow logic depends on — which you absolutely shouldn't be — then you may experience breakage. As a result, we may have to wait until 6.0 for a non-experimental release of this stuff.
That was a lot; congratulations to those of you still here. I am sure you have questions, and that's a good thing. This is the place to ask them, or to offer feedback on the design. Let's go!
I have a server on port 8000 that bundles and hosts svelte components, which are imported into a sveltekit app running on port 5173. This setup used to work fine on svelte 4, but the migration isnt as straight forward as i'd hoped. Let me show first how the old setup used to work server and client side, and then what ive tried so far.
The server had a esbuild bundler, which had its outputFiles hosted on a http get request.
export default async function bundle (entry) {
const build = await esbuild.build({
entryPoints: [entry],
mainFields: ["svelte", "browser", "module", "main"],
conditions: ["svelte", "browser"],
target: "es6",
format: "esm",
write: false,
treeShaking: true,
sourcemap: config.isDev ? "inline" : false,
minify: true,
bundle: true,
outdir: dirname(entry),
outExtension: { ".js": ".svelte" },
plugins: [
cache(svelteImportMap),
sveltePlugin({ },
compilerOptions: {
filename: basename(entry),
css: "injected",
},
}),
],
});
return build.outputFiles;
}
const svelte = "https://esm.sh/[email protected]";
const svelteImportMap = {
importmap: {
imports: {
svelte,
"@vivalence/ui": `../../../../packages/ui/mod.js`,
"svelte/store": `${svelte}/store`,
"svelte/motion": `${svelte}/motion`,
"svelte/internal": `${svelte}/internal`,
"svelte/internal/disclose-version": `${svelte}/internal/disclose-version`,
},
},
};
....
// serve:
bundler.serve = () => async (ctx) => {
const path = join(dirname(input.path), ctx.params.filename);
const bundle = await bundler(path);
if (bundle) {
ctx.response.body = bundle;
ctx.response.type = "application/javascript";
}
};
This was consumed by the client in two steps. The Widget functioned as the Sveltekits Universal interface/ loader.
<script>
import Component from "./Component.svelte";
import { onMount } from "svelte";
export let bundle;
export let data;
let component = null;
async function fetchAndCompileAST() {
const response = await locals.call.raw(bundle, null, { method: "GET" });
const text = await response.text();
const blob = new Blob([text], { type: "application/javascript" });
const url = URL.createObjectURL(blob);
const { default: Widget } = await import(/* @vite-ignore */ url);
component = Widget;
}
onMount(() => {
fetchAndCompileAST();
});
</script>
{#if Component}
<Component this="{component}" {...data} />
{:else}
<p>Loading component...</p>
{/if}
The referenced Component:
<script>
import { onDestroy } from 'svelte'
let component
export { component as this }
let target
let cmp
const create = () => {
cmp = new component({
target,
props: $$restProps,
})
}
const cleanup = () => {
if (!cmp) return
cmp.$destroy()
cmp = null
}
$: if (component && target) {
cleanup()
create()
}
$: if (cmp) {
cmp.$set($$restProps)
}
onDestroy(cleanup)
</script>
<div id="game-container" bind:this={target} />
The component thats gettings built by the server is currently an empty demo.
<script>
console.log("Hello World from Component");
</script>
<h1 class="text-palette-white">My Heading</h1>
this setup worked like a CHARM! given, its a bit much, but once i had figured it out, it never had any hickups.
but as you can see, it relied on instantiating the components as new component
classes.
now ive updated the build svelte dependency to 5.1.9 which is the same my main sveltekit app uses. on the client ive tried a few different approaches like:
replace new component
with cmp = createClassComponent({component:Component, target });
and cmp = mount(Component, { target, props: payload,});
but nothing works. I get various error messages like:
Uncaught TypeError: Cannot read properties of undefined (reading 'call')
in Component.svelte
in Widget.svelte
in GameBoard.svelte
in +page.svelte
in layout.svelte
in +layout.svelte
in root.svelte
at get_first_child (operations.js:77:28)
at template.js:48:50
at Flashcards (Flashcards.svelte:3:44)
at render.js:228:16
at update_reaction (runtime.js:317:53)
at update_effect (runtime.js:443:18)
at create_effect (effects.js:125:4)
at branch (effects.js:346:9)
at render.js:210:3
at update_reaction (:5173/.vite/deps/chunk-6CDLSX2F.js?v=6ab23a08:1714:23)TypeError: Cannot read properties of undefined (reading 'call')
at get_first_child (operations.js:77:28)
at template.js:48:50
at Flashcards (Flashcards.svelte:3:44)
at render.js:228:16
at update_reaction (runtime.js:317:53)
at update_effect (runtime.js:443:18)
at create_effect (effects.js:125:4)
at branch (effects.js:346:9)
at render.js:210:3
at update_reaction (runtime.js:317:53)
any help would be welcome. i am very stuck and have no clue what levers to try next.
above
No response
System:
OS: macOS 14.5
CPU: (8) arm64 Apple M1 Pro
Memory: 161.06 MB / 16.00 GB
Shell: 5.9 - /bin/zsh
Binaries:
Node: 20.17.0 - ~/.nvm/versions/node/v20.17.0/bin/node
Yarn: 4.5.0 - ~/.nvm/versions/node/v20.17.0/bin/yarn
npm: 10.8.2 - ~/.nvm/versions/node/v20.17.0/bin/npm
pnpm: 9.11.0 - ~/Library/pnpm/pnpm
Browsers:
Chrome: 130.0.6723.117
blocking an upgrade
Recently I was watching Rich on some video podcast and learned about the inspector feature. We've been using svelte for a year now and had no clue something like this exists, however it'd be really helpful for us.
So I am wondering why it isn't enabled by default? Or at least why it's not there in svelte.config.js
as a commented piece of config when you create project with npx sv
?
I've seen this topic mentioned in https://github.com/sveltejs/svelte/discussions/15822, but I feel it could be better discussed as a potential feature rather than a question.
Currently, if I need to pass a snippet as a parameter to a @render
tag, I need to declare it beforehand, like so:
{#snippet dataItem(name: string, value: Snippet)}
<div>
<dt>{dtValue}:</dt>
<dd>{@render ddValue()}</dd>
</div>
{/snippet}
{#snippet value()}<strong>ipsum</strong>{/snippet}
{@render dataItem("Lorem", value)}
On top of creating many single-use variables (if rendered more than once), which can lead to mistakes, it's also just a very labored way of doing it. It's problematic to the point where it almost makes more sense to copy-paste the contents of the snippet to avoid those issues.
One way to fix the problem would be to allow for @render
to receive children, like so:
{#render dataItem("Lorem", value)}
{#snippet value()}<strong>ipsum</strong>{/snippet}
{/render}
This approach would have a few problems:
@render
tag would now need to be written as #render
, though it could simply support both ways;Another way would be to pass anonymous snippets directly in the @render
call, like so:
{@render dataItem(
"Lorem",
{#snippet}<strong>ipsum</strong>{/snippet}
)}
This would circumvent the first two issues above, but it would mean blending HTML into what I think is supposed to only accept JS, which seems to be something Svelte managed to avoid doing up until now.
I had originally planned to post this as a feature request, but since I'm undecided as to which feature should be introduced, if any at all, I think I'll defer to the more experienced Svelte devs as to how to solve this problem.
After upgrading to Svelte 5, I’ve had this lingering thought about the implementation of Snippets that feels a bit odd.
Snippets are powerful, and I understand they are the successor to Slots. I didn’t have this feeling during the Slot era because Slots were relatively simple in functionality—too limited to make me overthink things (once again, kudos to the team for their amazing work).
Back to the main point: when using Snippets, their flexibility often makes me wonder—isn’t this just an anonymous Component?
Take the following code as an example:
{@render view({ name: "hello" })}
Why not replace it with this form:
<view name="hello"/>
From my perspective, a Snippet that accepts an object as its argument is structurally consistent with a regular Component. It feels similar to the difference between arrow functions and regular functions in JavaScript—I believe the two could share the same design. This way, Snippets would gain Component-like features such as richer parameter passing, better control over reactivity, type safety, and more.
We could also introduce a new syntax for Snippets to make them visually identical to Components:
{#snippet}
<script>
let { children, val1, val2 } = $props();
const v1 = $state(val1);
const v2 = $derived(val2);
</script>
...
{/snippet}
This would then be compiled into an anonymous Component and bundled with the parent Component.
I think this approach could significantly reduce the number of Component files—for example, scrollbar-root.svelte
, scrollbar-handler.svelte
, scrollbar-tracker.svelte
, etc., could all be consolidated into a single .svelte
file!
They could even detect closures to automatically generate certain reactive bindings, reducing boilerplate code when developing "large components" (cases where multiple components are used together) for passing contextual information.
Additionally, if the above proposal could be implemented, I believe the following idea would naturally follow:
Namely, allowing Snippets to be embedded directly into JavaScript.
Consider the role of JavaScript’s let/const
keywords and {@const ...}
in our framework—where let/const
are the prototypes, and {@const ...}
serves as a supplement to achieve similar functionality in HTML.
Ultimately, I think Snippets will follow the same path—where $snippet(...)
in JavaScript becomes the primary form, and {#snippet}
in HTML acts as a supplement.
If this could be realized, we would achieve first-class language-level Component facilities within the Svelte framework. This would completely overturn previous debates about defining multiple script blocks in a single file and eliminate React’s biggest advantage over Svelte—the syntactic consistency and closure-like behavior enabled by JSX.
Of course, my understanding of Svelte may not be deep enough, and my arguments might not hold water. But in any case, this is my personal perspective—one that might guide Svelte toward a more elegant and powerful future.
Hello, I realized that you can actually mock Svelte's enhance forms when you use Vitest browser mode but a blocker is that the decode function expects some decoders which is not present when testing, does anyone have a workaround?
I’ve been exploring Svelte 5’s get/set pairs for state bindings and find them an amazing way to avoid $effect and untrack. They offer clear, event-driven logic and reduce boilerplate.
However, I noticed that getters can be called multiple times per update cycle, and at unpredictable times, which makes them problematic for side effects like data fetching or expensive computations. This unpredictability limits their use for those cases.
Here’s a minimal reproduction demonstrating this: link.
Has anyone found reliable patterns or best practices to handle such scenarios? Would love to hear community thoughts or possible improvements to the reactive model.
I want to do things like this,
in layout file:
<!-- +layout.svelte -->
<script>
/** @type {import('./$types').LayoutData} */
let { title } = $props();
</script>
<svelte:head>
<title>{title}</title>
</svelte:head>
Then i can set title in the child +layout.svelte or +page.svelte just with a variable.
And i don't kown how to implement yet.
When binding the onclick
on Button
, you encounter the error:
can't access property "hind", get(...) is null in <unknown> in Button.svelte in App.svelte in __wrapper.svelte
—but as soon as you remove the {...props}
spread, it works normally. Is this behavior to be expected?
Note I’m not sure if this usage is appropriate:
let component: Component = $state(null!);
App.Svelte
<script lang="ts">
import Button from './Button.svelte'
import Component from './Component.svelte'
let component: Component = $state(null!)
</script>
<Button onclick={component.hind} disabled={true}/>
<Component bind:this={component}/>
Button.svelte
<script lang="ts">
let {onclick, ...props}: {onclick: () => void} = $props()
</script>
<button onclick={onclick} {...props}>Test</button>
Component.svelte
<script lang="ts">
export function hind() {}
</script>
<div></div>