Show Menu

Interested in contributing?

Submit Post

Share with the biggest community of Svelte enthusiasts in the world

Welcome to Svelte Society, homepage for everything Svelte. Find what you're looking for in the navigation above!

Become a sponsor

Support Svelte Society and get your company featured here. Contact us

recipe  posted by  Kevin Åberg Kultalahti

Preserving State During In-App Navigation in SvelteKit

SvelteKit provides a built-in Snapshots feature that captures ephemeral DOM state like scroll positions and input values. When a user fills out a form, navigates away, and then returns, their input can be automatically restored.

How Built-in Snapshots Work

You export a snapshot object from your +page.svelte with capture and restore methods:

<script lang="ts">
  import type { Snapshot } from './$types';

  let comment = $state('');

  export const snapshot: Snapshot<string> = {
    capture: () => comment,
    restore: (value) => comment = value
  };
</script>

<textarea bind:value={comment} />

The capture function runs when leaving the page, and restore runs when returning. The data is persisted to sessionStorage and tied to the browser's history stack.

The Limitation

There's a catch: built-in Snapshots only work when navigating via the browser's history stack, such as when the user leaves your site entirely and returns using the back button, or when refreshing the page. They do not trigger during normal in-app navigation between routes.

If a user is on /posts/new, clicks a link to /posts, and then navigates back to /posts/new, the built-in snapshot will not restore their draft. This is because SvelteKit's client-side router handles the navigation without touching the browser's history-based snapshot mechanism.

An In-App Alternative

To preserve state during in-app navigation, you can use SvelteKit's navigation lifecycle hooks directly:

import { beforeNavigate, afterNavigate } from '$app/navigation';

export class Snapshot<T> {
  constructor(capture: () => T, restore: (data: T) => void) {
    beforeNavigate((n) => {
      if (n.from?.url) {
        this.setSession(n.from.url.pathname, capture());
      }
    });

    afterNavigate((n) => {
      if (n.to?.url) {
        const data = this.getSession(n.to.url.pathname);
        if (data) restore(data);
      }
    });
  }

  private setSession(key: string, value: T): void {
    sessionStorage.setItem(key, JSON.stringify(value));
  }

  private getSession(key: string): T | null {
    const item = sessionStorage.getItem(key);
    if (item === null) return null;
    return JSON.parse(item) as T;
  }
}

Usage in a component:

<script lang="ts">
  import { Snapshot } from '$lib/snapshot';

  let formData = $state({ title: '', body: '' });

  new Snapshot(
    () => formData,
    (data) => formData = data
  );
</script>

<input bind:value={formData.title} placeholder="Title" />
<textarea bind:value={formData.body} placeholder="Body" />

This approach uses beforeNavigate to capture state keyed by the current URL pathname, and afterNavigate to restore it when returning to that route. The state persists in sessionStorage, surviving page refreshes as well.

Try it live on SvelteLab

When to Use This

This pattern is useful when you want to preserve transient UI state across navigation:

  • Draft form content (blog posts, comments, support tickets)
  • Partially completed multi-step wizards
  • Search filters or query parameters the user has configured
  • Scroll position within a long list or document
  • Expanded/collapsed state of accordions or tree views
  • Unsaved edits in an admin panel

Caveats

The data must be JSON-serializable. Avoid storing large objects, as they remain in sessionStorage for the duration of the session. For complex state that needs server persistence, consider saving drafts to your backend instead.

December 16, 2025

Newsletter

Stay up to date with the Svelte ecosystem.

  • This Week in Svelte — A weekly roundup of the best tutorials, libraries, and community highlights
  • Featured Jobs — Hand-picked Svelte job opportunities from top companies
  • Community News — Announcements, events, and updates from the Svelte team

Newsletter data is processed by Plunk, our email service provider. See our Privacy Policy.