πŸ”§ Error Fixes
Β· 2 min read
Last updated on

Remix: Loader Error β€” Unexpected Response


Error: Unexpected Server Error in loader

What causes this

A Remix loader function threw an unhandled error or returned an invalid response. Loaders run on the server before rendering a route, and if they fail, Remix shows this generic error instead of your page.

Common causes:

  • An API call or database query failed inside the loader
  • The loader doesn’t return a proper Response or json() result
  • Environment variables are missing on the server
  • A thrown Response has an unexpected status code
  • TypeScript type errors that only surface at runtime
  • The loader accidentally returns undefined

Fix 1: Always return a valid response

// ❌ Returns undefined if the condition is false
export async function loader({ params }: LoaderFunctionArgs) {
  if (params.id) {
    return json({ item: await getItem(params.id) });
  }
  // falls through β€” returns undefined
}

// βœ… Always return something
export async function loader({ params }: LoaderFunctionArgs) {
  if (!params.id) {
    throw new Response('Not Found', { status: 404 });
  }
  return json({ item: await getItem(params.id) });
}

Fix 2: Wrap in try/catch

export async function loader({ request }: LoaderFunctionArgs) {
  try {
    const data = await fetchFromAPI(request);
    return json(data);
  } catch (error) {
    console.error('Loader failed:', error);
    throw new Response('Failed to load data', { status: 500 });
  }
}

Throwing a Response lets Remix render your error boundary instead of the generic error page.

Fix 3: Check environment variables

export async function loader() {
  const apiKey = process.env.API_KEY;
  if (!apiKey) {
    throw new Error('API_KEY is not set'); // This causes the generic error
  }

  // βœ… Better: throw a Response so the error boundary catches it
  if (!apiKey) {
    throw new Response('Server configuration error', { status: 500 });
  }

  const data = await fetch(`https://api.example.com?key=${apiKey}`);
  return json(await data.json());
}

Make sure your .env file is loaded and your deployment platform has the variables set.

Fix 4: Add an ErrorBoundary

Give users a better experience when loaders fail:

export function ErrorBoundary() {
  const error = useRouteError();

  if (isRouteErrorResponse(error)) {
    return <div>Error {error.status}: {error.statusText}</div>;
  }

  return <div>Something went wrong. Please try again.</div>;
}

Fix 5: Check the server logs

The generic error page hides the real error. Check your terminal or deployment logs:

# Local dev β€” check the terminal running `remix dev`
# Vercel β€” check function logs
vercel logs --follow

# Fly.io
fly logs

The actual error message in the logs tells you exactly what went wrong.

How to prevent it

  • Always wrap external calls (APIs, databases) in try/catch inside loaders
  • Throw Response objects instead of Error objects β€” Remix handles them better
  • Add ErrorBoundary exports to every route that has a loader
  • Validate environment variables at startup, not inside individual loaders
  • Use invariant() from tiny-invariant for runtime assertions

Related: React Interview Questions Β· Tanstack Router Not Found Fix

πŸ“˜