Navigation and Hydration
Forge starts with server-rendered HTML, then upgrades the page into a client-side app. The same UI route definition drives both entry points, so a page works on direct load, refresh, and in-app navigation.
The Lifecycle
First request
The browser requests a URL. Forge matches the UI route, runs middleware and load functions, renders the Svelte tree on the server, and sends HTML with initial data.
Hydration
The browser bundle starts, reads the injected route/config data, hydrates the existing HTML, and connects page state, controller input, and link handling.
In-app navigation
Forge intercepts internal links, matches the next route, fetches JSON when the route needs server data, then swaps the active page tree.
Fallback
External links, missing routes, and normal browser behavior still work as links. Forge only upgrades routes it owns.
Links vs Programmatic Navigation
Prefer anchors for destinations. Use navigate() when navigation is the result of a completed action or decision.
<script lang="ts">
import { navigate } from "@noego/forge/client";
async function saveProject() {
await save();
navigate("/projects", { replace: true });
}
</script>
<a href="/projects/42">Project details</a>
<button onclick={saveProject}>Save and return</button>HTML First, JSON Later
The first request asks for HTML. Later internal navigations can ask the same route for JSON load data, then reuse the already-loaded client runtime.
GET /projects/42
Accept: text/html
# Forge returns:
# - SSR HTML
# - serialized load data
# - route metadata for hydration
GET /projects/43
Accept: application/json
# During client navigation, Forge can return:
# { "layout": [...], "view": {...} }Redirects
Load functions and middleware can redirect before rendering. Forge preserves the distinction between framework-controlled load requests and ordinary API calls so redirects do not unexpectedly hijack unrelated fetches.
// src/ui/pages/account/main.load.ts
export default async function load({ context }) {
if (!context.user) {
return Response.redirect("/login", 302);
}
return {
user: context.user
};
}Rules of Thumb
Use normal links for destinations
Anchors preserve accessibility, copy-link behavior, browser history, and full reload fallback.
Use navigate() after actions
Programmatic navigation is for saves, auth redirects, wizard steps, and other logic-driven transitions.
Keep load data deterministic
SSR and navigation should agree. Avoid random values, browser-only APIs, and timestamps that change every render.
Put slow secondary data elsewhere
Load should block only the first meaningful screen. Fetch heavy tables, streams, and optional panels after mount.