ESC
Type to search...
S
Soli Docs

Error Pages

Custom error handling for production mode with support for branded error pages.

Production-Ready Error Pages

Soli provides detailed error pages during development and clean, user-friendly pages in production. You can customize error pages to match your brand.

1 Development vs Production Mode

Development Mode

  • Full stack traces
  • Interactive REPL for debugging
  • View locals from render() on template errors
  • Request details (params, body, headers)
  • Source code preview
soli serve

The interactive REPL is token-protected and loopback-only by default. For a trusted local server accessed from another machine, opt in with SOLI_DEV_REPL_ALLOW_REMOTE=1 and a pinned SOLI_DEV_REPL_SECRET — the server refuses to start otherwise (SEC-051), and the secret is never embedded in the HTML error page.

Production Mode

  • Clean, branded error pages
  • No failure details leaked to the visitor
  • Error ID for support reference
  • Multi-line stderr block with full context
  • Customizable templates
soli serve --no-dev

Inspecting variables in the REPL

The REPL evaluates expressions against a snapshot of the environment captured the moment the request failed. The controller and request state is always present — this (including any @ivars you set), params, req, and cookies. When the error happened while a template was rendering, the data you passed to render(...) is captured too: each key as a top-level name, and the whole hash as _view_data.

def show
  @page_owner = "Olivier"
  render("posts/show", {
    "title": "Hello",
    "posts": Post.all()
  })
end

# Then, in the error-page REPL:
title                     # => "Hello"
posts.map(fn(p) p.title)  # locals are real values — run expressions, not just reads
_view_data.title          # same data, under the _view_data object
this.page_owner           # controller instance vars

When a view errored, the page also shows a View Locals panel with a one-click button per render(...) local (plus an All locals button for _view_data), so you don't have to remember the names.

A name passed to render(...) that collides with a controller-scope variable is shadowed by the controller value at the top level — reach the view's copy under _view_data.<key> (the View Locals buttons do this for you). Variables created inside the template (a <% total = ... %> assignment, a loop variable) are not part of the snapshot; only the locals you passed into render(...) are.

Error logging to stderr

For every 500 the server writes a multi-line block to stderr — useful when you only have access to container / journald logs. This fires in both --dev and --no-dev so failures show up in your terminal during development the same way they do in production. The first [ERROR] request_id=… METHOD PATH - msg line is preserved from earlier versions so existing log parsers keep working.

[ERROR] request_id=05dedb29-… GET /users/42 - Type error: cannot index null with string at 7:13
  stack:
    show at app/controllers/users.sl:92
    find at app/models/user.sl:14
  request:
    {
      "body": "[REDACTED]",
      "headers": { ... },
      "method": "GET",
      "params": { ... },
      "path": "/users/42",
      "query": { ... },
      "session": "N/A"
    }
  env: {"current_user": null, "user_id": null, ...}

Secrets are redacted in the stderr snapshot: Authorization and cookie / token / password params show [REDACTED]; the request body is always redacted. The env: line is filtered by the same rule, so a local variable named like a secret (password, api_key, access_token, …) is logged as [REDACTED] instead of by value. Failure context is written only to stderr — never to the production HTML page.

Correlate a customer's Error ID to a stderr block by searching the logs for request_id=<that id>.

2 Default Error Pages

Soli includes built-in error pages for common HTTP status codes:

Status Description
400 Bad Request
403 Forbidden
404 Not Found
405 Method Not Allowed
500 Internal Server Error
502 Bad Gateway
503 Service Unavailable

3 Custom Error Pages

Create custom error pages by placing templates in app/views/errors/.

Directory Structure
app/
 views/
  errors/
   400.html.slv    # Custom 400 Bad Request page
   403.html.slv    # Custom 403 Forbidden page
   404.html.slv    # Custom 404 Not Found page
   500.html.slv    # Custom 500 Internal Server Error page
   502.html.slv    # Custom 502 Bad Gateway page
   503.html.slv    # Custom 503 Service Unavailable page

Template Variables

Variable Type Description
status Number HTTP status code (e.g., 500)
message String Error message
request_id String Unique identifier for support

Example: Custom 404 Page

<div style="min-height: 100vh; display: flex; align-items: center; justify-content: center; background-color: #f8f9fa;">
  <div style="text-align: center;">
    <h1 style="font-size: 6rem; font-weight: bold; color: #343a40; margin-bottom: 1rem;"><%= status %></h1>
    <h2 style="font-size: 1.5rem; font-weight: 600; color: #6c757d; margin-bottom: 1rem;">Page Not Found</h2>
    <p style="color: #6c757d; margin-bottom: 2rem;"><%= message %></p>
    <a href="/" style="display: inline-block; padding: 0.75rem 1.5rem; background-color: #007bff; color: white; border-radius: 0.375rem;">Go Home</a>
    <p style="margin-top: 2rem; font-size: 0.75rem; color: #adb5bd;">Error ID: <%= request_id %></p>
  </div>
</div>

Example: Custom 500 Page

<div style="min-height: 100vh; display: flex; align-items: center; justify-content: center; background-color: #fff5f5;">
  <div style="text-align: center; max-width: 400px;">
    <h1 style="font-size: 3rem; font-weight: bold; color: #c53030; margin-bottom: 1rem;"><%= status %></h1>
    <h2 style="font-size: 1.25rem; font-weight: 600; color: #1a202c; margin-bottom: 1rem;">Something went wrong</h2>
    <p style="color: #4a5568; margin-bottom: 2rem;">We're sorry, but something unexpected happened.</p>
    <a href="/" style="display: inline-block; padding: 0.75rem 1.5rem; background-color: #c53030; color: white; border-radius: 0.375rem;">Return to Homepage</a>
    <p style="margin-top: 2rem; font-size: 0.75rem; color: #718096;">Reference ID: <%= request_id %></p>
  </div>
</div>
Using Tailwind CSS

If your application already uses Tailwind CSS, you can use Tailwind classes instead of inline styles for cleaner templates.

4 Important Notes

  • No Layouts

    Error pages render without application layouts to avoid potential cascading errors.

  • Optional

    Custom error pages are optional. If a template doesn't exist, Soli uses the default error page.

  • Full Template Features

    Error templates support all template features including conditionals, loops, and partials.

5 Production Deployment

# Using soli CLI
soli serve --no-dev

# Or set environment variable
SOLI_ENV=production soli serve

Production Mode Ensures:

  • Custom error pages are used
  • Auth headers, tokens, passwords, and request bodies are redacted
  • Error IDs are generated for support reference

Best Practices

Keep it Simple

Error pages should be clean and focused on helping users return to working pages.

Provide Navigation

Include links to help users return to working pages (home, back, etc.).

Log Error IDs

Store error IDs in your logs to correlate user reports with server errors.

Test Error Flows

Regularly test custom error pages to ensure they render correctly.