LumenJS provides built-in error pages and consistent error handling across loaders, API routes, and SSR. Errors are displayed with full details in development and sanitized in production.
When an unhandled error occurs during a request, LumenJS renders a styled error page automatically. These cover common HTTP status codes:
| Status | When it appears |
|---|---|
404 | API route not found, or no matching page module |
405 | API route exists but doesn't export the requested HTTP method |
500 | Unhandled exception in a loader, API handler, or SSR render |
For page routes, unmatched URLs fall back to the SPA shell (index.html) so the client-side router can handle them. API routes return JSON error responses.
If a loader() function throws, LumenJS catches the error and returns a JSON error response with the appropriate status code:
// pages/dashboard.ts export async function loader({ headers }) { const token = headers['authorization']; if (!token) { const err = new Error('Unauthorized'); (err as any).status = 401; throw err; } return { user: await getUser(token) }; }
The thrown error's status property sets the HTTP status code (defaults to 500), and the message property is returned in the JSON response.
{ __nk_redirect: true, location, status }), LumenJS sends a redirect response instead of an error.
API route handlers use the same pattern. Throw an error with a status property to control the response code:
// api/users/[id].ts export async function GET({ params }) { const user = await findUser(params.id); if (!user) { throw { status: 404, message: 'User not found' }; } return user; } export async function POST({ body }) { if (!body?.name) { throw { status: 400, message: 'Name is required' }; } return await createUser(body); }
The response format for API errors is always JSON:
{ "error": "User not found" }
If server-side rendering fails for a page, LumenJS automatically falls back to client-side rendering (CSR). The page still loads. It just renders in the browser instead of on the server.
This means an SSR error won't take your page offline. You'll see a warning in the server logs:
[LumenJS] SSR render failed, falling back to CSR: <error details>
Error behavior changes between development and production:
| Behavior | Development | Production |
|---|---|---|
| Stack traces in error pages | Shown in the error detail panel | Hidden from the response |
| Vite error overlay | Shown for build/transform errors | Not available |
| Console logging | Full error with stack trace | Full error with stack trace |
Use try/catch in loaders and API routes to handle expected failures gracefully:
// pages/profile.ts export async function loader({ params }) { try { const profile = await fetch(`https://api.example.com/users/${params.id}`); if (!profile.ok) { return { error: 'Profile not found', profile: null }; } return { profile: await profile.json(), error: null }; } catch { return { error: 'Service unavailable', profile: null }; } } export class PageProfile extends LitElement { static properties = { profile: { type: Object }, error: { type: String }, }; render() { if (this.error) { return html`<p>${this.error}</p>`; } return html`<h1>${this.profile?.name}</h1>`; } }
This pattern lets you render a meaningful UI for failures instead of showing a generic error page.