๐Ÿ”ง Error Fixes
ยท 6 min read
Last updated on

SvelteKit: Error in Load Function โ€” How to Fix It


500 Internal Error
Error in load function

Your SvelteKit load function threw an unhandled error. SvelteKit catches it and shows a generic 500 page. The actual error is in your terminal/server logs โ€” the browser only shows the sanitized message.

How SvelteKit load functions work

SvelteKit has two types of load functions:

  • +page.server.ts (server load) โ€” Runs only on the server. Can access databases, env vars, secrets. Never sent to the browser.
  • +page.ts (universal load) โ€” Runs on both server (SSR) and client (navigation). Canโ€™t access server-only resources.

Both must return an object. If they throw, SvelteKit shows the error page. If they return nothing, the page gets undefined data.

Fix 1: Add error handling to fetch calls

The most common cause โ€” an API call fails and you donโ€™t handle it.

// src/routes/+page.server.ts
import { error } from '@sveltejs/kit';

export async function load({ fetch }) {
  // โŒ No error handling โ€” any failure crashes the page
  const res = await fetch('/api/data');
  const data = await res.json();  // Crashes if response isn't JSON
  return { data };
}
// โœ… Handle errors properly
export async function load({ fetch }) {
  const res = await fetch('/api/data');
  
  if (!res.ok) {
    throw error(res.status, {
      message: 'Failed to load data',
      details: await res.text()
    });
  }
  
  try {
    return { data: await res.json() };
  } catch (e) {
    throw error(500, 'Invalid JSON response from API');
  }
}

Fix 2: Check your terminal for the real error

The browser shows a generic โ€œ500 Internal Errorโ€ for security (to avoid leaking stack traces). The actual error with full details is in your terminal where npm run dev is running.

# Terminal output shows the real error:
# TypeError: Cannot read properties of undefined (reading 'id')
#   at load (src/routes/posts/[slug]/+page.server.ts:12:24)

In production: Check your hosting platformโ€™s logs (Vercel Functions tab, Cloudflare Workers logs, or your serverโ€™s stdout).

Fix 3: Handle null/undefined data

If your load function depends on URL params or database lookups:

// src/routes/posts/[slug]/+page.server.ts
import { error } from '@sveltejs/kit';

export async function load({ params }) {
  const post = await db.post.findUnique({
    where: { slug: params.slug }
  });

  // โŒ post might be null โ€” accessing .title crashes
  return { title: post.title, content: post.content };

  // โœ… Handle the null case explicitly
  if (!post) {
    throw error(404, `Post "${params.slug}" not found`);
  }
  return { title: post.title, content: post.content };
}

Fix 4: Missing or wrong return value

Load functions must return a plain object. Forgetting to return, or returning the wrong type, causes errors.

// โŒ No return statement โ€” page.data is undefined
export async function load() {
  const data = await fetchData();
  // Forgot to return!
}

// โŒ Returning a non-object
export async function load() {
  return "hello";  // Must be an object
}

// โŒ Returning a Promise without await
export async function load() {
  return { data: fetchData() };  // data is a Promise, not the resolved value
}

// โœ… Correct
export async function load() {
  const data = await fetchData();
  return { data };
}

Fix 5: Environment variables missing

Server-side load functions can access env vars, but they might not be set in all environments.

import { env } from '$env/dynamic/private';
import { error } from '@sveltejs/kit';

export async function load() {
  if (!env.DATABASE_URL) {
    throw error(500, 'DATABASE_URL not configured');
  }
  
  if (!env.API_KEY) {
    throw error(500, 'API_KEY not configured');
  }
  
  const res = await fetch(env.API_URL, {
    headers: { Authorization: `Bearer ${env.API_KEY}` }
  });
  
  return { data: await res.json() };
}

Common issue: Env vars work in npm run dev (from .env) but fail in production because they werenโ€™t set in the hosting platformโ€™s environment settings.

Fix 6: Server load vs universal load confusion

Using server-only features in a universal load function:

// โŒ +page.ts (universal) โ€” can't access server-only modules
import { db } from '$lib/server/database';  // Error!

export async function load() {
  return { posts: await db.post.findMany() };
}

// โœ… Use +page.server.ts for server-only code
// src/routes/+page.server.ts
import { db } from '$lib/server/database';

export async function load() {
  return { posts: await db.post.findMany() };
}

Rule: If your load function needs databases, env vars, or secrets โ†’ use +page.server.ts. If it only needs fetch โ†’ use +page.ts (runs on both server and client, enabling client-side navigation without server round-trips).

Fix 7: Depends and invalidation issues

If you use depends() for invalidation, make sure the load function handles re-runs gracefully:

export async function load({ depends, fetch }) {
  depends('app:posts');  // Register dependency
  
  const res = await fetch('/api/posts');
  if (!res.ok) {
    // Don't throw on invalidation failures โ€” show stale data instead
    console.error('Failed to refresh posts');
    return { posts: [], error: 'Failed to load' };
  }
  return { posts: await res.json() };
}

Fix 8: Parent load function errors cascade

If a layoutโ€™s load function fails, all child pages fail too:

// src/routes/+layout.server.ts
export async function load({ locals }) {
  // If this throws, ALL pages under this layout get 500
  const user = await getUser(locals.session);
  return { user };
}

Fix: Make layout load functions resilient:

export async function load({ locals }) {
  try {
    const user = await getUser(locals.session);
    return { user };
  } catch {
    return { user: null };  // Don't crash all pages
  }
}

Add a proper error page

Create +error.svelte to show users a helpful error instead of the default:

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

<div class="error-page">
  <h1>{$page.status}</h1>
  <p>{$page.error?.message || 'Something went wrong'}</p>
  {#if $page.status === 404}
    <p>The page you're looking for doesn't exist.</p>
  {/if}
  <a href="/">Go home</a>
</div>

You can have error pages at different levels โ€” src/routes/+error.svelte catches all errors, while src/routes/dashboard/+error.svelte only catches errors in the dashboard section.

Debugging tips

  1. Always check the terminal โ€” the real error is there, not in the browser
  2. Add console.log at the start of your load function to confirm itโ€™s being called
  3. Use SvelteKitโ€™s handleError hook for centralized error logging:
    // src/hooks.server.ts
    export function handleError({ error, event }) {
      console.error('Unhandled error:', error);
      console.error('URL:', event.url.pathname);
      return { message: 'Internal error' };
    }
  4. Test with ?__data.json appended to the URL โ€” shows the raw load function output

FAQ

Why does my load function work in dev but fail in production?

Common causes: missing environment variables, different Node.js version, database not accessible from production server, or API URLs that differ between environments. Check your hosting platformโ€™s function logs.

Can I use try/catch inside load functions?

Yes, and you should. Wrap external calls in try/catch. If you want to show an error page, re-throw using SvelteKitโ€™s error() helper. If you want to show partial data, catch the error and return a fallback.

Whatโ€™s the difference between throwing error(404) and returning { status: 404 }?

throw error(404, 'message') triggers the error page (+error.svelte). Returning an object with a status property just passes that data to the page component โ€” it doesnโ€™t trigger the error page.

Why does my load function run twice?

During SSR, the server load runs first. Then on the client, the universal load runs again for hydration. If youโ€™re seeing double API calls, move the fetch to +page.server.ts (server-only) โ€” the data is serialized and sent to the client without re-fetching.