Remote Functions #13897
Replies: 78 comments 215 replies
-
yet another long- |
Beta Was this translation helpful? Give feedback.
-
The hype is real for this - amazing changes - and validation!!! Omg..
*cough* Zero *cough* 👀 |
Beta Was this translation helpful? Give feedback.
-
I am about to cry 🥹 This is a freaking great feature!. |
Beta Was this translation helpful? Give feedback.
-
Brilliant 🎉 |
Beta Was this translation helpful? Give feedback.
-
I'm excited to see the idea for validation! Have you considered something like this to avoid the extra import and slightly awkward nested functions? export const getStuff = query.validate(schema, async ({ id }) => {
// `id` is typed correctly. if the function
// was called with bad arguments, it will
// result in a 422 response
}); |
Beta Was this translation helpful? Give feedback.
-
wow! |
Beta Was this translation helpful? Give feedback.
-
Wow, this is awesome! It seems so intuitive and satisfying to use! |
Beta Was this translation helpful? Give feedback.
-
sounds great! I have been struggling to implement a fairly complex SvelteKit-app that renders sequences of sensor data from lidars, cameras and radars. each frame in a sequence can be 20-100 mb. at the same time, the sensor data is pretty static so caching can and will be used on as many levels as possible. super interested in what you have planned for caching!
this would be great and it would basically replace what I have implemented on my own to cache requests. it would be great with control over at least max size when used as some LRU-cache. but also max size in terms of size on the disk and maybe also some TTL. and of course some way to manually invalidate the cache per function or globally, if needed.
yeah this would also be useful! I would even go one step further and consider some sort of persistent cache for users - maybe in IndexedDB or something along those lines? otherwise that is something I plan to implement anyways - so I don’t have to stream hundreds of megabytes to a user if they accidentally refresh the page. again one thing less I would have to implement in userland so if this was provided as some opt-in feature by sveltekit it would be the dream. the same level of configuration I brought up for the server caching would be useful in a persistent client side cache as well —- please let me know if I can provide feedback in any more structured way, I would love to test this out in practice later on and help out as much as I can |
Beta Was this translation helpful? Give feedback.
-
This reminds me of the days I was a GWT expert, back in 2010-16. I must say there were disappointments with the RPC calls architecture, but I forgot the use cases. What I remember is that it was frustrating to call them manually, eg. from a mobile app, so I had to isolate then as only callers to service methods (having them like boilerplate code). Maybe the architecture would allow a non proprietary protocol so that it can be implemented in case of need... |
Beta Was this translation helpful? Give feedback.
-
Where can I subscribe so that I get a notification once this is testable? The form part is incredibly valuable <3 |
Beta Was this translation helpful? Give feedback.
-
This is going to be incredible, this and async address all of the features I have been wanting from Svelte. One question for the team, have you considered adding a <script>
import { getLikes, addLike } from './data.remote';
let { item } = $props();
</script>
<addLike.Form enhance={async () => {...}}>
<input type="hidden" name="id" value={item.id} />
<button>add like</button>
</addLike.Form>
<p>likes: {await getLikes(item.id)}</p> |
Beta Was this translation helpful? Give feedback.
-
if |
Beta Was this translation helpful? Give feedback.
-
EDIT: Ignore this. I didn't see it was a template string.
|
Beta Was this translation helpful? Give feedback.
-
While I don't think I will use this many times since I build mostly true SPAs with a separate backend, I really believe these will be very useful for fullstack apps or backend-for-frontend apps. There are two additions I would add: Output validationNot so much due to the validation but due to the validator being able to transform the output (setting default values for I think a tRPC-like signature like this would be more ergonomic if output validation is added but the signature is definitely not a big deal: const my_procedure = rpc
.input(input_schema)
.output(output_schema)
.query(schema, async (input) => {
//body
})
//maybe allow chaining a .mutation here to create a mutation that implicitly invalidates the query? Support for middleware (using the tRPC-like signature above)Middlewares are a great way to manage dependency injection in a declarative manner. const my_procedure = rpc
.input(input_schema)
.output(output_schema)
.middleware(services) //Whatever is returned from a middleware is passed to the next middleware as the first parameter
.middleware(logging)
.middleware(stats)
.middleware(with_cache({storage: 'redis', ttl: 3600})) //The function returned by with_cache() would be accessing a redis service returned by the first middleware
.query(schema, async (input, ctx) => { //ctx holds the awaited return value of the last middleware
//body
}) I also would like to know what are the plans regarding Personally I think |
Beta Was this translation helpful? Give feedback.
-
Why would this ever be needed / isn't this what TypeScript is for? You're already in control of the data you return from the function, so just... don't return data in the wrong shape / type the result of your function. (Am I missing something?)
Is this necessary in a world where you have access to |
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
I noticed that command would throw serialization errors when we try to send unicode data like emojis. Apparently character limitations of the underlying atob/btoa functions. |
Beta Was this translation helpful? Give feedback.
-
Tanstack Query's refetchOnWindowFocus option is enabled by default and is very valuable imo, is that something that can be provided by the framework? It could be implemented in userland but document visibility can be janky iirc. |
Beta Was this translation helpful? Give feedback.
-
I'm planning on svelte project where the same code deploys on multiple server with different subdomain for fault-tolerance. Is there any possibility to allow cross-origin remote function call? |
Beta Was this translation helpful? Give feedback.
-
I think you can enable this via server hooks? If you're referring to cross
site submissions the only workaround I know of atm is setting checkOrigin
to false
…On Sat, 28 Jun 2025 at 6:57 PM Irsyad Aminuddin ***@***.***> wrote:
I'm planning on svelte project where the same code deploys on multiple
server with different subdomain for fault-tolerance. Is there any
possibility to allow cross-origin remote function call?
—
Reply to this email directly, view it on GitHub
<#13897 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ACFY5BKGAG2CGYDI3O5Q3E33F23PFAVCNFSM6AAAAAB7QZ57FKVHI2DSMVQWIX3LMV43URDJONRXK43TNFXW4Q3PNVWWK3TUHMYTGNRQGU3DINQ>
.
You are receiving this because you commented.Message ID:
***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
This will be useful @shivan-eyespace. |
Beta Was this translation helpful? Give feedback.
-
i hope command will use devalue |
Beta Was this translation helpful? Give feedback.
-
It would be nice if remote functions with validation performed the validation on the client as well to avoid a wasteful trip, especially form actions |
Beta Was this translation helpful? Give feedback.
-
Maybe a stupid question, but do remote functions work without a server? I know it's implied in the name that they live on the server... but wondering if they would still work for pure SPAs that call other APIs client side, or if there are plans to offer a similar functionality as the |
Beta Was this translation helpful? Give feedback.
-
Thats actually very awesome. A problem for me was always that form actions are always had a coupling to the route and this fixes it. Great work. |
Beta Was this translation helpful? Give feedback.
-
Hi, great work, I love it! Do you have any idea of a release date or timeframe for when this feature might be available as experimental? Just curious if we're talking weeks, months, or next year. |
Beta Was this translation helpful? Give feedback.
-
About streaming and realtime applications: My main usecase would displaying changes to all visitors of a given site. I currently work on a site where race results (round times on a track) get uploaded for attendees to look at. Once uploaded & processed the new race results should displayed. Without having to do setinterval.fetching... |
Beta Was this translation helpful? Give feedback.
-
Steaming and real-time use-cases in our application:
|
Beta Was this translation helpful? Give feedback.
-
I wanted to share my current setup with streaming response from I use a combination of I have and endpoint which generates a summary of a document. This is the return from the ...
const geminiResponse = await gemini.models.generateContentStream({
...
})
const stream = new ReadableStream({
async start(controller) {
for await (const chunk of geminiResponse) {
send(controller, chunk.text);
}
controller.close();
},
});
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
},
}); and then on the client I do export async function * streamReader<T = any>(stream: ReadableStream<Uint8Array<ArrayBufferLike>>) {
let buffer = '';
const reader = stream.pipeThrough(new TextDecoderStream()).getReader();
for (;;) {
const { value, done } = await reader.read();
if (done) break;
buffer += value;
let idx;
while ((idx = buffer.indexOf('\n\n')) !== -1) {
const raw = buffer.slice(0, idx).trim();
buffer = buffer.slice(idx + 2);
if (!raw || raw.startsWith(':')) continue; // heartbeat/comment
let data = JSON.parse(raw.replace(/^data:\s*/, '')) as T;
yield data;
}
}
}
...
const call = await fetch(url, { method: 'POST' });
for await (const chunk of streamReader<string>(call.body)) {
...
} It works pretty well but I had some problems with closing the stream and In addition if I need to re-observe an operation in progress I use a very similar setup but with SSE. Hope it helps :). |
Beta Was this translation helpful? Give feedback.
-
Aside from the .remote.ts filename conversion, it'll be great if we can also adopt a |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
tl;dr
Remote functions are a new concept in SvelteKit that allow you to declare functions inside a
.remote.ts
file, import them inside Svelte components and call them like regular functions. On the server they work like regular functions (and can access environment variables and database clients and so on), while on the client they become wrappers aroundfetch
. If you're familiar with RPC and 'server functions', this is basically our take on the concept, that addresses some of the drawbacks we've encountered with other implementations of the idea.You can try it out soon by installing from the corresponding PR once it's out...
... and add the experimental options
We'll also link an example here, soon.
Background
Today, SvelteKit's data loading is based on the concept of loaders. You declare a
load
function inside a+page/layout(.server).ts
file, fetch the required data for the whole page in it, and retrieve the result via thedata
prop inside the sibling+page/layout.svelte
file.This allows for a very structured approach to data loading and works well for sites where the loaded data is used on the whole page. When that isn't so clear-cut, some drawbacks become apparent:
Additionally, since
load
and the resultingdata
prop are somewhat disconnected, we have to resort to very clever but somewhat weird solutions like generating hidden types you import as./$types
or even using a TypeScript plugin to avoid having to import the types yourself. An approach where we can use TypeScript natively would simplify all this and make it more robust.Lastly, apart from form actions SvelteKit doesn't give you a good way to mutate data. You can use
+server.ts
files and do fetch requests against these endpoints, but it's a lot of ceremony and you lose type safety.Asynchronous Svelte
A couple of weeks ago we introduced Asynchronous Svelte, a proposal to allow using
await
at the top level of Svelte components and inside the template.This in itself is already valuable, but the way SvelteKit's data loading is architected right now you can't take full advantage of it inside SvelteKit.
Requirements
A solution should fix these drawbacks and take advantage of Svelte's capabilities, and specifically should:
An important additional requirement is that modules that can run in the client (including components) must never include code that can only run on the server. A remote function must be able to safely access things like database clients and environment variables that should not (or cannot) be accessed from the client.
In practice, this means that remote functions must be declared in a separate module. Over the last few years various systems have experimented with 'server functions' declared alongside universal/client code, and we're relieved to see a growing consensus that this is a flawed approach that trades security and clarity for a modicum of convenience. You're one innocent mistake away from leaking sensitive information (such as API keys or the shape of your database), and even if tooling successfully treeshakes it away, it may remain in sourcemaps. While no framework can completely prevent you from spilling secrets, we think colocating server and client code makes it much more likely.
Allowing server functions to be declared in arbitrary locations also masks the fact that they are effectively creating a publicly accessible endpoint. Even in systems that prevent server functions from being declared in client code (such as
"use server"
in React Server Components), experienced developers can be caught out. We prefer a design that emphasises the public nature of remote functions rather than the fact that they run on the server, and avoids any confusion around lexical scope.Design
Remote functions are declared inside a
.remote.ts
file. You can import them inside Svelte components and call them like regular async functions. On the server you import them directly; on the client, the module is transformed into a collection of functions that request data from the server.Today we’re introducing four types of remote function:
query
,form
,command
andprerender
.query
Queries are for reading dynamic data from the server. They can have any number of arguments. The arguments are serialized with devalue, which handles types like
Date
andMap
in addition to JSON, and takes the transport hook into account.When called during server-rendering, the result is serialized into the HTML payload so that the data isn't requested again during hydration.
Queries are thenable, meaning they can be awaited. But they're not just promises, they also provide properties like
loading
andcurrent
(which contains the most recent value, but is initiallyundefined
) and methods likeoverride(...)
(see the section on optimistic UI, below) andrefresh()
, which fetches new data from the server. We’ll see an example of that in a moment.Query objects are cached in memory for as long as they are actively used, using the serialized arguments as a key — in other words
myQuery(id) === myQuery(id)
. Refreshing or overriding a query will update every occurrence of it on the page. We use Svelte's reactivity system to intelligently clear the cache to avoid memory leaks.form
Forms are the preferred way to write data to the server:
A form object such as
addLike
has enumerable properties —method
,action
andonsubmit
— that can be spread onto a<form>
element. This allows the form to work without JavaScript (i.e. it submits data and reloads the page), but it will also automatically progressively enhance the form, submitting data without reloading the entire page.By default, all queries used on the page (along with any
load
functions) are automatically refreshed following a form submission, meaninggetLikes(...)
will show updated data.In addition to the enumerable properties,
addLike
has non-enumerable properties such asresult
, containing the return value, andenhance
which allows us to customize how the form is progressively enhanced. We can use this to indicate that onlygetLikes(...)
should be refreshed, and to provide nicer behaviour in the case that the submission fails (by default, an error page will be shown):We can go one step further and enable single-flight mutations — meaning that the updated data for
getLikes(...)
is sent back from the server along with the form result — by moving therefresh
call to the server:import { query, form } from '$app/server'; import * as db from '$lib/server/db'; export const getLikes = query(async (id: string) => { const [row] = await sql`select likes from item where id = ${id}`; return row.likes; }); export const addLike = form(async (data: FormData) => { const id = data.get('id') as string; await sql` update item set likes = likes + 1 where id = ${id} `; + await getLikes(id).refresh(); // we can return arbitrary data from a form function return { success: true }; });
command
For cases where serving no-JS users is impractical or undesirable,
command
offers an alternative way to write data to the server.This time, simply call
addLike
, from (for example) an event handler:As with forms, we can refresh associated queries on the server during the command for a single-flight mutation, or on the client after the command has run, otherwise all queries will automatically be refreshed.
prerender
This function is like
query
except that it will be invoked at build time to prerender the result. Use this for data that changes at most once per redeployment.You can use
prerender
functions on pages that are otherwise dynamic, allowing for partial prerendering of your data. This results in very fast navigation, since prerendered data can live on a CDN along with your other static assets.Prerendering is automatic, driven by SvelteKit's crawler, but you can also provide an
entries
option to control what gets prerendered, in case some pages cannot be reached by the crawler:If the function is called at runtime with arguments that were not prerendered it will error by default, as the code will not have been included in the server bundle. You can set
dynamic: true
to change this behaviour:import { prerender } from '$app/server'; export const getBlogPost = prerender((slug: string) => { // ... }, { + dynamic: true, entries: () => [ ['first-post'], ['second-post'], ['third-post'] ] });
Optimistic updates
Queries have an
override
method, which is useful for optimistic updates. It receives a function that transforms the query, and returns a function that removes the override:Multiple overrides can be applied simultaneously — if you click the button multiple times, the number of likes will increment accordingly. If
addLike()
fails, releasing the override will decrement it again, otherwise the updated data (sans override) will match the optimistic update.Validation
Data validation can be performed with the
validate
function, which accepts a Standard Schema object and a callback:Accessing the current request event
SvelteKit exposes a function called
getRequestEvent
which allows you to get details of the current request inside hooks,load
, actions, server endpoints, and the functions they call.This function can now also be used in
query
,form
andcommand
, allowing us to do things like reading and writing cookies:Note that some properties of
RequestEvent
are different in remote functions. There are noparams
orroute.id
, and you cannot set headers (other than writing cookies, and then only insideform
andcommand
functions), andurl.pathname
is always/
(since the path that’s actually being requested by the client is purely an implementation detail).Redirects
Inside
query
,form
andprerender
functions it is possible to use theredirect(...)
function. It is not possible insidecommand
functions, as you should avoid redirecting here. (If you absolutely have to, you can return a{ redirect: location }
object and deal with it in the client.)Future work / open questions
Server caching
We want to provide some kind of caching mechanism down the line, which would give you the speed of prerendering data while also reacting to changes dynamically. If you're using Vercel, think of it as ISR on a function level.
We would love to hear your opinions on this matter and gather feedback around the other functions before committing to a solution.
Client caching
Right now a query is cached and deduplicated as long as there's one active subscription to it. Maybe you want to keep things around in memory a little longer, for example to make back/forward navigation instantaneous? We haven't explored this yet (but have some ideas) and would love to hear your use cases (or lack thereof) around this.
Prerendered data could be kept in memory as long as the page is open — since we know it’s unchanging, it never needs to be refetched. The downside is that the memory could then never be freed up. Perhaps this needs to be configurable.
Conversely, for queries that we know will become stale after a certain period of time, it would be useful if the query function could communicate to the client that it should be refetched after
n
seconds.Batching
We intend to add client-side batching (so that data from multiple queries is fetched in a single HTTP request) and server-side batching that solves the n + 1 problem, though this is not yet implemented.
Streaming
For real-time applications, we have a sketch of a primitive for streaming data from the server. We’d love to hear your use cases.
Beta Was this translation helpful? Give feedback.
All reactions