This blog post focuses on using form-based remote functions in SvelteKit to handle server-side filtering in a component-based architecture. It explains why this approach was chosen, how data flows between the server and components, and which pitfalls to avoid.
To keep the post concise, it does not include code examples. Instead, a GitHub repository with a fully working form-based remote function and inline comments is provided for reference.
live link
Files in the Repository
The following files and directories are needed for this project:
src/lib/remote/src/lib/server/src/lib/components/src/routes/(form-filter)/form-filter/+layout.server.tssrc/routes/(form-filter)/form-filter/+page.sveltesvelte.config.jsYou can change the directory to your preference as long as it is inside lib. I chose this structure because I plan to experiment with additional remote functions here as well
Remote functions are a tool for type-safe communication between client and server. They can be called anywhere in your app, but always run on the server, meaning they can safely access server-only modules containing things like environment variables and database clients.
Combined with Svelte’s experimental support for await, it allows you to load and manipulate data directly inside your components.
In practice, this means you write server logic once, and components can call that logic directly
SvelteKit remote functions come in four official flavours: query, form, command, and prerender. Each one is designed for a different kind of client–server interaction.
This blog is about using the form.
form is the right choice when you want to submit data from an HTML <form>.
What it’s good at
<form> submissionsFormData on the serverIn this project, I initially got the filtering working without remote functions. The checkboxes submitted correctly (the URL got updated with the filter request), but after applying filters I only saw the updated results after a full page reload. I’m not entirely sure why it behaved this way in my setup.
Instead of forcing a manual refresh, I followed my teachers’ advice and explored whether remote functions could solve it in a cleaner way.
That turned out to be a great fit, because the project was structured around components:
With a remote function, I can:
Because both components use the same remote function, the filtering logic stays remains on the server and does not depend on +page.svelte.
This meansthat when the remote function is called:
To implement this setup, you need:
svelte.config.js.your-function.remote.ts or your-function.remote.js.I placed the data fetching logic in a shared location so it can be used by both layout.server.js and the remote function
While writing this, I realized I could also have written a separate fetch directly inside the remote function, but this makes the fetch reusable.
src/lib/server/projectData.js, I defined a function that fetches the full projects dataset.layout.server.js. From there, the complete project list is passed down to the result component as initial data.At the time I was working on this, there were very few practical examples available onlinefor my specific case.
There were, however, many examples of login implementations
Reading the documentation was difficult for me, and the lack of concrete examples made it hard to understand the concepts at first. I read the Svelte documentation multiple times, but I still did not fully understand how remote functions were meant to work.
These are the things I did not understand at first or did incorrectly:
page.server.js and layout.server.js.*.remote.ts.import { form } from '$app/server';).+layout.server.js, but later realized that this data is sent to the client, so remote functions cannot access it<form> and <input> elements.action attribute on the form would be sufficient, but this does not work with form based remote functions. You need something like this$effect, $state, and $derived) to ensure the results reactively update whenever the remote function is called.While the learning curve was steeper than expected, once I understood the missing pieces, the purpose of remote functions became much clearer. At first, I did not see how they were helpful or better than other approaches.
That changed after discussing it with a teacher, who pointed out that remote functions can be called directly from components, allowing those components to work independently from each other.
In my first working version, I called the remote function from the Filter component and then passed the filtered list through +page.svelte to the results component. Using a shared remote function instead removed that dependency and resulted in a cleaner, more component-focused architecture.