A comprehensive adapter that enables Svelte 5 as a rendering framework for TanStack Start and TanStack Router. Use TanStack Start's full-stack conventions — file-based routing, type-safe server functions, SSR — with Svelte 5's runes and component model instead of React.
| Package | Description |
|---|---|
@tanstack/svelte-router |
Router components (Link, Outlet, RouterProvider), navigation hooks (useParams, useNavigate, useLoaderData, etc.), and SSR utilities for Svelte 5 |
@tanstack/svelte-start |
Full-stack framework — Vite plugin, server function support (createServerFn), SSR handlers, and client hydration |
┌─────────────────────────────────────────────────┐
│ Your App │
│ .svelte routes + createServerFn + router.ts │
├─────────────────────────────────────────────────┤
│ @tanstack/svelte-start │
│ Vite plugin · SSR handlers · server functions │
├─────────────────────────────────────────────────┤
│ @tanstack/svelte-router │
│ Components · Hooks · Context · SSR rendering │
├─────────────────────────────────────────────────┤
│ @tanstack/router-core │
│ Route tree · Matching · History · Loaders │
└─────────────────────────────────────────────────┘
This mirrors the architecture of TanStack Start's official React and Solid adapters — a thin Svelte binding layer on top of the framework-agnostic router core.
bun add @tanstack/svelte-router @tanstack/svelte-start svelte
bun add -D @sveltejs/vite-plugin-svelte vite typescript
// vite.config.ts
import { defineConfig } from "vite";
import { tanstackStart } from "@tanstack/svelte-start/plugin/vite";
export default defineConfig({
plugins: [tanstackStart()],
});
// src/routes/__root.ts
import { createRootRoute } from "@tanstack/svelte-router";
import Root from "./__root.svelte";
export const rootRoute = createRootRoute({
component: Root,
});
<!-- src/routes/__root.svelte -->
<script lang="ts">
import { Outlet, Link } from '@tanstack/svelte-router'
</script>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</nav>
<Outlet />
// src/routes/index.ts
import { createRoute } from "@tanstack/svelte-router";
import { rootRoute } from "./__root";
import Home from "./index.svelte";
export const indexRoute = createRoute({
getParentRoute: () => rootRoute,
path: "/",
component: Home,
});
<!-- src/routes/index.svelte -->
<script lang="ts">
let count = $state(0)
</script>
<h1>Hello TanStack Start + Svelte!</h1>
<button onclick={() => count++}>Count: {count}</button>
// src/router.ts
import { createRouter } from "@tanstack/svelte-router";
import { routeTree } from "./routeTree.gen";
export function createRouter() {
return createRouter({
routeTree,
defaultPreload: "intent",
});
}
declare module "@tanstack/svelte-router" {
interface Register {
router: ReturnType<typeof createRouter>;
}
}
// src/entry-client.ts
import { hydrateStart } from "@tanstack/svelte-start/client";
import { createRouter } from "./router";
hydrateStart({ router: createRouter() });
// src/entry-server.ts
import {
createStartHandler,
defaultStreamHandler,
} from "@tanstack/svelte-start/server";
import { createRouter } from "./router";
export default createStartHandler({
createRouter,
handler: defaultStreamHandler,
});
Use createServerFn for type-safe server-client RPC:
// src/functions/getUsers.ts
import { createServerFn } from "@tanstack/svelte-start";
export const getUsers = createServerFn({ method: "GET" }).handler(async () => {
const users = await db.user.findMany();
return users;
});
export const createUser = createServerFn({ method: "POST" })
.validator((data: { name: string; email: string }) => data)
.handler(async ({ input }) => {
return db.user.create({ data: input });
});
Use in route loaders:
// src/routes/users.ts
import { createRoute } from "@tanstack/svelte-router";
import { getUsers } from "../functions/getUsers";
export const usersRoute = createRoute({
getParentRoute: () => rootRoute,
path: "/users",
component: Users,
loader: () => getUsers(),
});
Access in components:
<!-- src/routes/users.svelte -->
<script lang="ts">
import { useLoaderData } from '@tanstack/svelte-router'
const users = useLoaderData({ from: '/users' })
</script>
{#each users as user}
<p>{user.name}</p>
{/each}
<RouterProvider>Root component that provides the router context to all child components.
<script lang="ts">
import { RouterProvider } from '@tanstack/svelte-router'
let { router } = $props()
</script>
<RouterProvider {router} />
<Link>Type-safe navigation link with preloading, active state detection, and accessible attributes.
<Link to="/users/$userId" params={{ userId: '123' }} activeProps={{ class: 'active' }}>
View User
</Link>
<Outlet>Renders child route components within layout routes.
<nav><!-- navigation --></nav>
<Outlet />
<Navigate>Imperative redirect — navigates on mount.
<Navigate to="/login" />
| Hook | Description |
|---|---|
useRouter() |
Access the router instance |
useRouterState(opts) |
Subscribe to router state with a selector |
useNavigate(opts?) |
Get a navigate function |
useParams(opts) |
Access route params |
useSearch(opts) |
Access search/query params |
useMatch(opts) |
Access the current route match |
useLoaderData(opts) |
Access loader data for a route |
useLoaderDeps(opts) |
Access loader deps for a route |
useLocation() |
Access the current parsed location |
useRouteContext(opts) |
Access route context data |
useCanGoBack() |
Check if history allows going back |
useMatchRoute() |
Get a function to check if a path matches |
The adapter provides full SSR with client hydration:
render() from svelte/server (synchronous) wrapped in streaming Responsehydrate() from svelte attaches to server-rendered HTMLwindow.__TSR_DEHYDRATED__ for client pickup| Handler | Description |
|---|---|
defaultStreamHandler |
Renders to streaming Response (recommended) |
defaultRenderHandler |
Renders to complete string Response |
createStartHandler |
Factory for creating custom SSR handlers |
import { createNodeHandler } from "@tanstack/svelte-start/server-handler"; // Node.js
import { createBunHandler } from "@tanstack/svelte-start/server-handler"; // Bun
import { createDenoHandler } from "@tanstack/svelte-start/server-handler"; // Deno
packages/
svelte-router/ # @tanstack/svelte-router
src/
components/ # Svelte 5 components (Link, Outlet, etc.)
hooks/ # Navigation & state hooks
context/ # Router context (Svelte setContext/getContext)
ssr/ # Server rendering & client hydration
index.ts # Main barrel export
svelte-start/ # @tanstack/svelte-start
src/
plugin/ # Vite plugin (tanstackStart())
client/ # StartClient + hydrateStart
server/ # StartServer + stream/render handlers
server-handler/ # Runtime adapter helpers (Node, Bun, Deno)
client-runtime/ # Client runtime utilities
defaults/ # Default entry point templates
index.ts # Main entry (createServerFn, useServerFn)
config.ts # defineStartConfig helper
examples/
svelte/
start-basic/ # Basic example app with Svelte 5 + TanStack Start
Synchronous SSR: Svelte 5's render() is synchronous (unlike React's streaming SSR). We achieve streaming at the data level via the router's dehydration system.
Context API: Uses Svelte's native setContext/getContext instead of React's Context.Provider pattern. Simpler and more idiomatic.
Runes-based Hooks: All hooks use Svelte 5's $state and $derived runes for reactivity, not stores or legacy $: syntax.
Component Rendering: Route components are .svelte files rendered via svelte:component. The Match component handles the recursive rendering tree.
Vite Plugin Composition: tanstackStart() composes @sveltejs/vite-plugin-svelte automatically — users only need one plugin call.
# Install dependencies
bun install
# Build all packages
bun run build
# Run example app
bun run dev:example
# Type check
bun run typecheck
This adapter is experimental. It mirrors the architecture of TanStack Start's official Solid and Vue adapters and targets feature parity with the React adapter.
.svelte files (currently manual)<script module> co-located route config (Svelte-specific pattern)@tanstack/start-plugin-core (requires upstream changes)MIT