Gerson

Gerson

Passionate developer specializing in web development, cloud architecture, and system design.

TypeScriptReactNext.jsPythonFastAPISQLNode.jsAWS

SvelteKit Remote Functions: A React Dev's Look at Type-Safe Server Calls

SvelteKit's remote functions let you call server code from anywhere in your app with end-to-end type safety and no API layer. From a React and Next.js background, here is what clicks and what is genuinely new.

A modern web development dashboard representing a full-stack framework

I spend most of my time in React and Next.js, so I did not expect a SvelteKit feature to be the thing that made me rethink how I wire a frontend to a backend. But remote functions — which stabilized alongside Svelte 5.49 and matured through the 5.55 / SvelteKit 2.57 releases in mid-2026 — are the cleanest take on "call the server from the client, type-safely, without building an API" that I have used.

If you know React Server Actions or tRPC, you already have the concept. Remote functions just draw the boundary in a way that is hard to misuse.

What a remote function is

You write a function in a .remote.ts file. It always runs on the server — so it can safely import your database client, read environment secrets, and touch server-only modules — but you import and call it directly from client components as if it were local. SvelteKit generates the fetch, serialization, and types in between. There is no route handler to write, no request/response shape to invent, no client to keep in sync.

There are a few kinds. query reads data. form handles submissions and progressive enhancement. command runs an arbitrary mutation. Here is a data read and a form mutation living side by side:

src/routes/posts.remote.ts

import { query, form } from "$app/server";
import { db } from "$lib/server/db";
import * as v from "valibot";

// runs on the server; callable from any component
export const getPosts = query(async () => {
  return db.post.findMany({ orderBy: { createdAt: "desc" } });
});

export const createPost = form(
  v.object({ title: v.string(), body: v.string() }),
  async ({ title, body }) => {
    await db.post.create({ data: { title, body } });
    // tell dependent queries to refresh
    await getPosts().refresh();
  },
);

Notice the validation schema on createPost: the input is parsed on the server before your handler runs, so the argument you receive is already typed and trusted.

Using them in a component

On the client, a query is just a value you can await, and a form is an object you spread onto a <form> element. The form works before hydration — progressive enhancement is the default, not an add-on.

src/routes/+page.svelte

<script>
  import { getPosts, createPost } from "./posts.remote";
</script>

<form {...createPost}>
  <input name="title" />
  <textarea name="body"></textarea>
  <button>Publish</button>
</form>

{#await getPosts()}
  <p>Loading...</p>
{:then posts}
  {#each posts as post}
    <article>{post.title}</article>
  {/each}
{/await}

The parts that are genuinely new

Two things stood out coming from Next.js. First, batching: query.batch collapses multiple calls that happen in the same tick into one request, which quietly kills the waterfall problem you get when a dozen components each fetch their own slice. Second, experimental async: with it enabled you can await directly in markup without wrapping everything in a pending boundary, which makes the await-in-a-component ergonomics feel closer to a synchronous render than anything I have used.

Would I reach for it

If I were starting a SvelteKit app, without hesitation — it removes an entire category of glue code (route handlers, client fetchers, shared types) and replaces it with functions you import. The React equivalent, Server Actions plus a data library, gets you most of the way, but remote functions feel more like one idea applied consistently than several ideas stitched together. It is worth an afternoon even if you never ship Svelte, just to steal the mental model.