Svelte and SvelteKit course ยท Module 11: SvelteKit Routing

Error Pages and Special Pages

2 min read ยท
In this lesson4

+error.svelte

<!-- src/routes/+error.svelte -->
<script>
  import { page } from '$app/state';
</script>

<h1>Error {page.status}</h1>
<p>{page.error?.message}</p>

<a href="/">Back to the home page</a>

The page object from $app/state is reactive and you read it without a $ sign. The old $page store from the $app/stores module was removed in SvelteKit 3.

Raising errors

// src/routes/warriors/[id]/+page.js
import { error } from '@sveltejs/kit';

export function load({ params }) {
  const warrior = getWarrior(params.id);

  if (!warrior) {
    error(404, 'Warrior not found');
  }

  return { warrior };
}

The error() function throws by itself, so you do not write throw in front of it (that was the SvelteKit 1 form). The second argument is always the message text.

+page.js vs +page.server.js

// +page.js - runs on server AND client
export function load({ params, fetch }) {
  // Can be called during client-side navigation
  return { data };
}
// +page.server.js - server ONLY
export function load({ params, locals }) {
  // Access to database, secrets, etc.
  return { data };
}

Redirects

import { redirect } from '@sveltejs/kit';

export function load({ locals }) {
  if (!locals.user) {
    redirect(303, '/login');
  }

  return { user: locals.user };
}

Just like error(), redirect() throws by itself - no throw needed.

Code for this lesson: App.svelte
<script>
  // Programmatic navigation and loading states

  let loading = $state(false);
  let currentPage = $state('home');

  async function navigateTo(page) {
    loading = true;

    // Simulating data loading
    await new Promise(r => setTimeout(r, 1000));

    currentPage = page;
    loading = false;
  }

  // Simulating beforeNavigate
  function handleBeforeNavigate(e, page) {
    if (currentPage === 'form' && !confirm('Leave the page?')) {
      e.preventDefault();
      return;
    }
    navigateTo(page);
  }
</script>

<main class="navigation-demo">
  <h1>SvelteKit Navigation</h1>

  <nav class="nav">
    <button onclick={(e) => handleBeforeNavigate(e, 'home')}>Home</button>
    <button onclick={(e) => handleBeforeNavigate(e, 'warriors')}>Warriors</button>
    <button onclick={(e) => handleBeforeNavigate(e, 'form')}>Form</button>
  </nav>

  {#if loading}
    <div class="loading">
      <div class="spinner"></div>
      <p>Loading...</p>
    </div>
  {:else}
    <section class="content">
      {#if currentPage === 'home'}
        <h2>Home page</h2>
        <p>Welcome to the app!</p>
      {:else if currentPage === 'warriors'}
        <h2>Warriors</h2>
        <p>List loaded from server</p>
      {:else if currentPage === 'form'}
        <h2>Form</h2>
        <p>Leaving will cause data loss!</p>
        <input placeholder="Type something..." />
      {/if}
    </section>
  {/if}

  <section class="code-example">
    <h2>Programmatic navigation</h2>
    <pre>
import {"{"} goto {"}"} from '$app/navigation';

// Simple navigation
goto('/warriors');

// With options
goto('/warriors', {"{"}
  replace: true,
  refreshAll: true
{"}"});
    </pre>
  </section>
</main>

<style>
  .navigation-demo {
    font-family: 'Georgia', serif;
    max-width: 600px;
    margin: 0 auto;
    padding: 2rem;
    background: #0a1628;
    color: #b8d4f7;
    min-height: 100vh;
  }

  h1 {
    color: #4a90d9;
    text-align: center;
  }

  .nav {
    display: flex;
    justify-content: center;
    gap: 0.5rem;
    margin-bottom: 2rem;
  }

  button {
    padding: 0.75rem 1.5rem;
    background: #1e3a5f;
    color: #b8d4f7;
    border: 1px solid #4a90d9;
    border-radius: 4px;
    cursor: pointer;
  }

  button:hover {
    background: #2a4a6f;
  }

  .loading {
    text-align: center;
    padding: 3rem;
  }

  .spinner {
    width: 50px;
    height: 50px;
    border: 4px solid #1e3a5f;
    border-top-color: #4a90d9;
    border-radius: 50%;
    animation: spin 1s linear infinite;
    margin: 0 auto 1rem;
  }

  @keyframes spin {
    to { transform: rotate(360deg); }
  }

  section {
    background: rgba(74, 144, 217, 0.1);
    border: 1px solid #4a90d9;
    border-radius: 8px;
    padding: 1rem;
    margin: 1rem 0;
  }

  h2 {
    color: #4a90d9;
    margin-top: 0;
  }

  input {
    width: 100%;
    padding: 0.75rem;
    background: rgba(0,0,0,0.3);
    border: 1px solid #4a90d9;
    border-radius: 4px;
    color: #b8d4f7;
    margin-top: 0.5rem;
  }

  pre {
    background: rgba(0,0,0,0.5);
    padding: 1rem;
    border-radius: 4px;
    font-size: 0.85rem;
    overflow-x: auto;
  }
</style>

Docs entries used in this lesson

Spotted a mistake in this lesson?

Check yourself

Answer the questions from this lesson. Pick an answer to see right away whether it is correct.

  1. 1. How do you raise a 404 error in a load function (SvelteKit 3)?

Useful articles