Error handling
The Boring JavaScript Stack handles errors in two different modes:
- development errors are for developers and should show useful debugging context
- production errors are for users and should render calm, branded error pages
For Inertia requests, this matters because the failing request usually happens over XHR. In development, Inertia shows non-Inertia error responses in its error modal so you can see the real failure without digging through the Network tab.
Development errors
In development (NODE_ENV !== 'production'), unexpected server errors are rendered with Youch.
Youch gives Boring Stack apps a rich stack trace with source snippets, request context, and readable frames. When the failure happens during an Inertia visit or form submission, Inertia displays that Youch HTML inside its development error modal. When the same failure happens on a normal browser visit, the browser shows the Youch page directly.
The development error response includes useful debugging context:
- HTTP status code
- error name and message
- request method and URL
- stack frames with source snippets
- sanitized headers, params, query, body, and session data
Sensitive values such as cookies, authorization headers, CSRF tokens, passwords, secrets, and session-looking values are redacted before they are shown. Even in development, this keeps screenshots, recordings, and copied error reports safer.
Inertia's modal flash
Inertia opens its development error modal and then writes the error HTML into an iframe. On slower paints, you may briefly see the modal shell before Youch finishes rendering. This is an upstream modal timing detail, not a Boring Stack error-handling requirement. We do not ship an extra client patch for it.
Content Security Policy
If your app uses a strict Content Security Policy that blocks inline styles, pass a nonce to createInertiaApp() so Inertia can attach it to the style tag used by the development error modal. This helps the modal styles load under CSP, but it does not change server error handling.
Production errors
In production, users should not see stack traces or internal exception messages.
inertia-sails renders a configured Inertia error page for the common production statuses:
403404500503
The Boring Stack templates ship an error page under assets/js/pages/error.*. That page receives:
{
status: 404,
title: 'Page not found',
message: 'The page you are looking for could not be found.'
}This keeps production errors inside your app's visual language while still letting Sails handle the HTTP status code correctly.
Using the serverError response
Define the serverError exit in your action:
module.exports = {
exits: {
success: {
responseType: 'inertia'
},
serverError: {
responseType: 'serverError'
}
},
fn: async function () {
try {
const data = await someRiskyOperation()
return { page: 'dashboard', props: { data } }
} catch (error) {
throw { serverError: error }
}
}
}Direct usage
You can also call handleServerError directly:
module.exports = {
fn: async function () {
try {
await dangerousOperation()
} catch (error) {
return sails.inertia.handleServerError(this.req, this.res, error)
}
}
}Behavior by environment
| Environment | Inertia request | Non-Inertia request |
|---|---|---|
| Development | Youch inside the Inertia development modal | Full Youch HTML page |
| Production | Inertia error page for configured status | Inertia error page when configured, otherwise EJS/JSON |
Hybrid apps and Sails error pages
Boring Stack apps can be hybrid: some routes return Inertia pages and other routes still render traditional Sails/EJS views. Keep the normal Sails 404.ejs and 500.ejs pages for non-Inertia requests. They are still the right fallback for direct browser visits, server-rendered EJS routes, crawlers, and any page that is not controlled by the Inertia client.
Only add Inertia-aware notFound or forbidden responses when you specifically want Sails responses to render through the Inertia status-page policy. If errorPage is configured, full-page browser requests can render the Inertia error component too. If your app wants EJS for non-Inertia requests and Inertia error pages only for X-Inertia requests, branch in your custom response:
// api/responses/notFound.js
module.exports = function notFound(error) {
if (this.req.header('X-Inertia')) {
return this.req._sails.inertia.handleErrorPage(this.req, this.res, {
statusCode: 404,
error
})
}
return this.res.status(404).view('404', { error })
}A good rule is:
- non-Inertia request: use the Sails/EJS response
- Inertia request: either redirect/flash, show the development error modal, or render an Inertia error page if the app has one
Do not replace Sails' built-in error handling globally unless every route in the app is truly Inertia-only.
Custom responses
The Boring Stack templates include notFound and forbidden responses that route production-friendly statuses through the Inertia error page:
// api/responses/notFound.js
module.exports = function notFound(error) {
return this.req._sails.inertia.handleErrorPage(this.req, this.res, {
statusCode: 404,
error
})
}Use the same shape for other status pages that should render through Inertia.
Customizing development errors
The development error HTML is generated by Youch. If you need to customize it, you can create your own response handler:
// api/responses/serverError.js
module.exports = function serverError(error) {
const sails = this.req._sails
const isDev = process.env.NODE_ENV !== 'production'
const isInertia = this.req.header('X-Inertia')
if (isInertia && isDev) {
return this.res.status(500).send(buildCustomErrorHtml(error))
}
// Fall back to default handling
return sails.inertia.handleServerError(this.req, this.res, error)
}Global error handling
For catching unhandled errors globally, you can use Sails' config/http.js middleware:
// config/http.js
module.exports.http = {
middleware: {
order: [
// ... other middleware
'errorHandler'
],
errorHandler: function (err, req, res, next) {
if (req.header('X-Inertia')) {
return sails.inertia.handleServerError(req, res, err)
}
return next(err)
}
}
}Error handling best practices
- Use specific exits: Define clear exit types for different error scenarios
exits: {
success: { responseType: 'inertia' },
notFound: { responseType: 'notFound' },
badRequest: { responseType: 'badRequest' },
serverError: { responseType: 'serverError' }
}- Log errors: Always log server errors for monitoring
catch (error) {
sails.log.error('Payment processing failed:', error)
throw { serverError: error }
}- User-friendly production errors: Use the shared error page for status pages and flash messages for recoverable failures
catch (error) {
sails.log.error('Operation failed:', error)
sails.inertia.flash('error', 'Something went wrong. Please try again.')
return '/dashboard'
}Validation errors vs server errors
| Type | Response | Use case |
|---|---|---|
badRequest | 400 + redirect with errors | Form validation failures |
serverError | 500 + error modal/redirect | Unexpected exceptions |
Use badRequest for validation errors (user can fix) and serverError for unexpected failures (bugs, external service failures).