Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/jpcutshall/svelte5-router/llms.txt

Use this file to discover all available pages before exploring further.

svelte5-router is designed to work in server-side rendering (SSR) environments such as SvelteKit or a custom Node.js server. The key is the url prop on <Router>: when it receives a truthy string, the router ignores window.location and uses that string as the current pathname instead, allowing routes to be matched synchronously during server rendering before any DOM exists.

How SSR Routing Works

On the server, there is no window or browser history. svelte5-router detects this with typeof window === "undefined" and switches to a synchronous registration path:
  1. Each <Route> registers itself immediately when the component initialises (no onMount).
  2. As soon as a route is registered that matches url, the router sets hasActiveRoute = true and ignores all subsequent registrations.
  3. The rendered HTML contains only the markup for the matching route, ready to hydrate on the client.
In the browser, a falsy url (the "" default) is silently ignored and window.history is used as normal.
SSR sets the active route synchronously via a hasActiveRoute flag. The first matching route wins, so the order of <Route> declarations can matter in SSR if two routes could match the same URL — give more specific routes a more specific path to ensure the right one fires first.

The url Prop Pattern

The recommended approach is to accept url as an optional prop in your top-level component. In the browser the prop is omitted so url defaults to "" (falsy and ignored). On the server, the parent or entry point passes the real request pathname:
App.svelte
<script>
  import { Router, Link, Route } from "svelte5-router";
  import Home from "./routes/Home.svelte";
  import About from "./routes/About.svelte";
  import Blog from "./routes/Blog.svelte";

  interface Props {
    url?: string;
  }

  // In the browser, url is omitted so it defaults to "" (falsy and ignored).
  // On the server, a parent component (e.g. SvelteKit's +page.svelte) passes
  // the real request URL as a prop.
  let { url = "" }: Props = $props();
</script>

<Router {url}>
  <nav>
    <Link to="/">Home</Link>
    <Link to="/about">About</Link>
    <Link to="/blog">Blog</Link>
  </nav>

  <main>
    <Route path="/blog" component={Blog} />
    <Route path="/about" component={About} />
    <Route path="/"><Home /></Route>
  </main>
</Router>

SvelteKit Integration

SvelteKit renders each page on the server before sending it to the browser. Pass the request URL from the server-side load function into your App component via props.
1

Create a layout or page component

Use SvelteKit’s $page store (or a load function) to obtain the current pathname and forward it to your router.
+page.svelte
<script>
  import App from "../App.svelte";
  import { page } from "$app/stores";

  // $page.url.pathname gives the server-side request path, e.g. "/about"
</script>

<App url={$page.url.pathname} />
2

Use a load function for full control

Alternatively, return the URL from a +page.server.ts load function and receive it as a prop:
+page.server.ts
import type { PageServerLoad } from "./$types";

export const load: PageServerLoad = ({ url }) => {
  return { url: url.pathname };
};
+page.svelte
<script>
  import App from "../App.svelte";

  interface Props {
    data: { url: string };
  }

  let { data }: Props = $props();
</script>

<App url={data.url} />
3

Mount the app on the client

In your browser entry point, you do not need to pass a URL — the empty default is correct:
main.ts
import App from "./App.svelte";
import { mount } from "svelte";

const app = mount(App, {
  target: document.getElementById("app")!,
  // No `url` prop needed here — the Router reads window.history
});

export default app;

Custom Node.js Server Example

If you are not using SvelteKit, you can render the app to HTML with Svelte’s render utility and pass the request URL directly:
import { render } from "svelte/server";
import App from "./App.svelte";

function handleRequest(requestUrl: string): string {
  const { html, head } = render(App, {
    props: { url: requestUrl },
  });

  return `<!DOCTYPE html>
<html>
  <head>${head}</head>
  <body>
    <div id="app">${html}</div>
  </body>
</html>`;
}

// Example with Node's built-in http module
import { createServer } from "http";

createServer((req, res) => {
  const html = handleRequest(req.url ?? "/");
  res.writeHead(200, { "Content-Type": "text/html" });
  res.end(html);
}).listen(3000);

Hydration

After the server sends the HTML, Svelte hydrates the page in the browser. Because url defaults to "" (falsy), the Router ignores it and uses window.location to determine the active route. The hydrated state matches the server-rendered HTML, so no layout shift occurs.
When using dynamic (lazy-loaded) routes with SSR, the route component chunk may not be available synchronously on the server. Prefer eager imports for routes that need to be server-rendered, and reserve dynamic() for client-only sections. See the Lazy Loading guide for details.

Router Internals: SSR vs Browser

StepBrowserServer
Location sourcewindow.historyurl prop
Route registrationonMount (async)Synchronous, immediate
Stops after first match?No — all routes registerYes — hasActiveRoute flag
Scroll resetYes, via window.scrollToSkipped (canUseDOM() returns false)

Build docs developers (and LLMs) love