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
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
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...
AsyncLocalStorageAn amazing feature has been released in the javascript server ecosystem - notably, the
AsyncLocalStorageprimitive which I believe meaningfully changes the game for how frameworks are built and implemented on the server-side.Is
AsyncLocalStoragewidely supported?AsyncLocalStorageappears 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_hooksnode:async_hooksnode:async_hooksnode:async_hooksnode:async_hooksWhat does this change?
AsyncLocalStorageis 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
AsyncLocalStorageis 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.
AsyncLocalStorageeliminates 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
AsyncLocalStoragecan 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
AsyncLocalStoragea 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.tsAfter
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.tssrc/lib/database.svelte.tsBetter 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
handleinsrc/hooks.server.tsto validate the user session.usertoevent.locals.userusertype it in theAppnamespace insrc/app.d.tsparent()or look upevent.localsin a+(page|layout).server.tsfileAfter
handleinsrc/hooks.server.tsto validate the user session.importit from anywhere (.ts,.svelte.ts,.svelte)hooks.server.tssrc/lib/user.svelte.tsInternals
Better load functions
Building upon my proposal within the following issue, @giacomoran's issue and Hugo's proposal:
loadfunctions #9160 (comment)loadfunctions #9160 (comment)Before
loadfunctions in SvelteKit run twice, once for server-side load, and once during mount.fetchrequest/responses via URL's which are serialized to the clientWebSocket's connections (eg: SurrealDB)Math.random())loadfunctions mutating state in.svelte.tsfiles leads to race-conditions that can leak state between requests.invalidatepart or parts of specific load functionsAfter
loadfunctions run once on initial server-side load, with all states transferred to the clientfetch,WebSockets,ServiceWorkerhooks, etc)Transport APIfor serializing non-pojosloadfunctions can safely mutate isolated states in.svelte.tsfiles thanks to request-aware state/stores.invalidates are allowed.Tip
invalidate,parentand 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
invalidatefunctions, ensuring code refactors don't accidentally break things, and enabling amazing features and functionality previously not possible./products/+page.ts/checkout/+page.tsTyped invalidate functions
Generated types
depends(...)IDs in theirApp.LoadTypeinterface (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.svelteAsynchronous components and asynchronous server-side rendering
Related issue:
The Current Limitation
Right now, Svelte’s SSR is designed to run synchronously. That is:
.sveltecomponent, 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:
loadfunctions (or+page.ts,+layout.ts) to handle allasyncdata fetching. If a component itself needs data, it’s forced to rely on theloadfunction or pass props down.awaitin 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-levelawaitin 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.svelteTip
Why this matters
Less reliance on
loadfunctions+page.tsor+layout.ts”, you can placeasyncfetching directly in component<script>s.loadfunctions for most+page.sveltefiles for when only simple async data may be needed for a specific componentMore ergonomic, maintainable code
awaitexactly where they need the data.Cleaner parallelization
loadchain (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
loadfunctions 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
awaitcalls, SvelteKit’s SSR engine could:AsyncLocalStoragecontext, we avoid mixing up states from concurrent requests.Putting it all together
With request-level isolation courtesy of
AsyncLocalStorageand 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.tsfile (an API endpoint), you typically have this signature:+server.tsOr maybe you have a function in
$lib/whatever.tsthat needs the same references passed in:+server.tsAnd in every module that needs request-specific details — like user authentication, cookies, database instances, or svelte's specialized
fetchfunction — 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/requestmodule$app/requesta module that can be imported from any(component).svelte+server.ts,+(page|layout).tsfiles.cookies- a universal cookies module forsetting andgetting cookies (available on the client side too, except forhttpOnlycookies)symbol- low level access to the symbol primitive, which returnsnullfor client-side code.fetch- a global sveltefetchwrapper, so you don't need to pass it down everywhere in your appurl- a global, reactive request-awareURLroute- a global reactive{ id: RouteId }params- global request parameters object (eg:/app/blog/[post] => { post: string })Plus more ideas...
+server.tsresponses withfetch#11108Beta Was this translation helpful? Give feedback.
All reactions