SvelteKit 3 Ideas and Discussions #13336
AlbertMarashi
started this conversation in
Ideas
Replies: 2 comments
-
I like the global The rest doesn't seem like it affects me either way, except for the idea of "async subroutines" which sounds like the kind of thing that took down Python's |
Beta Was this translation helpful? Give feedback.
0 replies
-
I like the async components and renders and top level await |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
I'd like to come here to suggest some changes to SvelteKit which I believe would benefit everyone.
I think almost everyone can agree that we use Svelte because we like writing code that is:
And we all love to use a framework that:
First...
AsyncLocalStorage
An amazing feature has been released in the javascript server ecosystem - notably, the
AsyncLocalStorage
primitive which I believe meaningfully changes the game for how frameworks are built and implemented on the server-side.Is
AsyncLocalStorage
widely supported?AsyncLocalStorage
appears to be supported by all server runtimes, but feel free to let me know if anything is missing from the list or is not supported.node:async_hooks
node:async_hooks
node:async_hooks
node:async_hooks
node:async_hooks
What does this change?
AsyncLocalStorage
is a major enabler for SSR frameworks because it finally makes truly isolated, request-level states and storage simple to implement:No More Global Leaks
In traditional server-side applications, it’s hard to store data that only belongs to a single request. If you naively put it in a module-scoped variable, updates to it can leak across multiple concurrent requests because everything in JS runs via a single process. With
AsyncLocalStorage
, each request can have its own “sandbox” so that data never collides or leaks.Native built-in primitive
Since
AsyncLocalStorage
is a built-in primitive of server-side runtime APIs, it removes the need for complicated hacks to manage and maintain state. Available across all major server runtimes as far as I am aware.Improved Developer Experience
For SSR applications, especially those with reactivity you often need to attach data or context to a user’s session or request.
Previously, you had to manually pass that data through each level of your application code or rely on less-reliable and unsafe global objects.
AsyncLocalStorage
eliminates the repeated boilerplate by letting you write once at the top level, and then access that request-level data anywhere else in your code—even deep in nested components or modules.Safer & More Performant
Because each request automatically could get its own isolated “storage” context, concurrency issues can be drastically reduced by eliminating waterfalls from
await parent()
's that load things like database resources. At the same time, you don’t need to verbosely pass down contexts and data down deeply to maintain isolation, helping you maintain and manage code in ways that are cleaner and less verbose.Bridging Client and Server States
AsyncLocalStorage
can help us unify how we handle state on the server with how you handle it in the client. Rather than worrying about whether some state is safe to update, we can write request-aware state stores that automatically maintain uniqueness across concurrent SSR calls. This opens the door to more natural, universal reactivity—mirroring the client’s store mechanism while preserving security and correctness on the server side.Writing server-side code should feel much like writing client-side code where possible.
Related discussions and issues:
Tip
All of these benefits—no global leaks, built-in support, improved DX, safer concurrency, and better bridging between client and server—make
AsyncLocalStorage
a game-changer for SSR applications like those written with SvelteKit.Examples
Note
In the following section, I'll run through some examples and scenarios that run through the changes these features might allow
Less boilerplate, more parallelization
Before
SvelteKit encourages passing data from one load function to another (
await parent()
chaining). This sometimes leads to waterfalls if multiple sub-layouts must wait on each other because they all rely on some root layout defined modules.src/routes/+layout.ts
- initialize the user-authenticated database at the top layersrc/routes/app/+layout.ts
- make some data available to all sub-pages and layouts of dashboardsrc/routes/app/org/[org]/+layout.ts
After
src/routes/+layout.ts
- initialize the user-authenticated database at the top layersrc/routes/app/+layout.ts
- make some data available to all sub-pages and layouts of dashboardsrc/routes/app/org/[org]/+layout.ts
src/lib/database.svelte.ts
Better and easier to use global states
You have a typical authentication system that gets a session cookie. During SSR, you need to figure out which user is logged in and pass that info to your routes and components.
Before
handle
insrc/hooks.server.ts
to validate the user session.user
toevent.locals.user
user
type it in theApp
namespace insrc/app.d.ts
parent()
or look upevent.locals
in a+(page|layout).server.ts
fileAfter
handle
insrc/hooks.server.ts
to validate the user session.import
it from anywhere (.ts
,.svelte.ts
,.svelte
)hooks.server.ts
src/lib/user.svelte.ts
Internals
Better load functions
Building upon my proposal within the following issue, @giacomoran's issue and Hugo's proposal:
load
functions #9160 (comment)load
functions #9160 (comment)Before
load
functions in SvelteKit run twice, once for server-side load, and once during mount.fetch
request/responses via URL's which are serialized to the clientWebSocket
's connections (eg: SurrealDB)Math.random()
)load
functions mutating state in.svelte.ts
files leads to race-conditions that can leak state between requests.invalidate
part or parts of specific load functionsAfter
load
functions run once on initial server-side load, with all states transferred to the clientfetch
,WebSockets
,ServiceWorker
hooks, etc)Transport API
for serializing non-pojosload
functions can safely mutate isolated states in.svelte.ts
files thanks to request-aware state/stores.invalidate
s are allowed.Tip
invalidate
,parent
and dependency improvementsBuilding upon Hugo's proposal, see his issue for more context:
Using typescript module augmentation features, we can enforce the type-safety of Svelte's
invalidate
functions, ensuring code refactors don't accidentally break things, and enabling amazing features and functionality previously not possible./products/+page.ts
/checkout/+page.ts
Typed invalidate functions
Generated types
depends(...)
IDs in theirApp.LoadType
interface (but, almost nobody would ever need to use this)loadDependency(dep: App.LoadTypeId)
thingyWhy not just bin
parent()
all together? we almost always want some specific data from parent load and layout functions.Explicitly declaring dependencies in this way allows us to be a lot more explicit and correct in regards to module dependencies
/+layout.ts
/app/dashboard/+page.ts
/app/dashboard/+page.svelte
Asynchronous components and asynchronous server-side rendering
Related issue:
The Current Limitation
Right now, Svelte’s SSR is designed to run synchronously. That is:
.svelte
component, it doesn’t expect to wait on any promises inside of<script>
code or the template itself.{#await promise}{...}{/await}
blocks are typically meant for client-side logic, so that the server doesn’t block or fork the SSR pipeline.{#await promise}
can work on the server-side with streaming promises, as far as I'm aware, this will result in the data not appearing on the first render, which can negatively impact SEO, as asynchronous indexing is delayed with most search engines...While this design works, it leads to some awkward patterns:
load
functions (or+page.ts
,+layout.ts
) to handle allasync
data fetching. If a component itself needs data, it’s forced to rely on theload
function or pass props down.await
in its<script>
code is out of luck.Key reason for these constraints (I believe): SSR concurrency is complicated when multiple components each do asynchronous tasks at once — especially if they each use, and mutate state during rendering operations...
Eliminating the Synchronous Requirement
If we trust that each SSR request is isolated, SvelteKit (or a future version) could allow a “root-level async render,” meaning:
{#await promise}{...}{/await}
or a top-levelawait
in a<script>
block), the rendering engine can pause, wait for thepromise
, and then resume/continue generating HTML.How it might look in code
src/routes/app/org/[org]/members/+page.svelte
Tip
Why this matters
Less reliance on
load
functions+page.ts
or+layout.ts
”, you can placeasync
fetching directly in component<script>
s.load
functions for most+page.svelte
files for when only simple async data may be needed for a specific componentMore ergonomic, maintainable code
await
exactly where they need the data.Cleaner parallelization
load
chain (no need to do things likeawait Promise.all(get_user_promise, get_member_promise)
or etc in your load functions)Universal code and faster client-side navigation
load
functions before rendering everything - instead, they can immediately start rendering the page with whatever data becomes availableAsyncLocalStorage
, mutating and accessing state in different modules can be safely isolated on the server-side.Parallel
{#await ...}
s on server-sideIf multiple components are each doing their own
await
calls, SvelteKit’s SSR engine could:AsyncLocalStorage
context, we avoid mixing up states from concurrent requests.Putting it all together
With request-level isolation courtesy of
AsyncLocalStorage
and an asynchronous SSR pipeline, SvelteKit (or a future Svelte version) could allow truly async components in SSR<script>
can have await statements.{#await promise}
blocks can fully resolve server-side.This new paradigm would bring SvelteKit closer to a world where the boundary between server and client is simpler, more consistent, and more powerful—without giving up the hallmark performance and simplicity Svelte is known for.
Cleaner, simpler server code and APIs
In SvelteKit today, when you create a
+server.ts
file (an API endpoint), you typically have this signature:+server.ts
Or maybe you have a function in
$lib/whatever.ts
that needs the same references passed in:+server.ts
And in every module that needs request-specific details — like user authentication, cookies, database instances, or svelte's specialized
fetch
function — you must pass them as parameters.This leads to:
The solution
$lib/whatever.ts
- Insidedo_whatever
(or any deeper library call), we just call the getters available under the"$app/request"
module:$app/request
module$app/request
a module that can be imported from any(component).svelte
+server.ts
,+(page|layout).ts
files.cookies
- a universal cookies module forset
ting andget
ting cookies (available on the client side too, except forhttpOnly
cookies)symbol
- low level access to the symbol primitive, which returnsnull
for client-side code.fetch
- a global sveltefetch
wrapper, so you don't need to pass it down everywhere in your appurl
- a global, reactive request-awareURL
route
- a global reactive{ id: RouteId }
params
- global request parameters object (eg:/app/blog/[post] => { post: string }
)Plus more ideas...
+server.ts
responses withfetch
#11108Beta Was this translation helpful? Give feedback.
All reactions