v1.2.0
inertia-sails v1.2.0
Locals — Sails-idiomatic view data
This release replaces the viewData API with locals, aligning inertia-sails with how Sails.js natively handles view data. In Sails, the second argument to res.view() is an object of locals — each key becomes a top-level variable in your EJS template. inertia-sails now works the same way.
What changed
The viewData key in action returns is now locals:
// Before
return {
page: 'courses/show',
props: { course },
viewData: {
title: course.title,
description: course.description,
ogImage: course.thumbnailUrl
}
}
// After
return {
page: 'courses/show',
props: { course },
locals: {
title: course.title,
description: course.description,
ogImage: course.thumbnailUrl
}
}The API methods on sails.inertia have been renamed:
| Before | After |
|---|---|
sails.inertia.viewData(key, value) |
sails.inertia.local(key, value) |
sails.inertia.viewDataGlobally(key, value) |
sails.inertia.localGlobally(key, value) |
sails.inertia.getViewData(key) |
sails.inertia.getLocals(key) |
Locals are now spread directly into res.view()
Previously, all view data was nested under a viewData key, requiring awkward access patterns in EJS:
<!-- Before: nested, requires typeof guard -->
<title><%= typeof viewData !== 'undefined' && viewData.title ? viewData.title : 'My App' %></title>Now locals are spread directly, so each one is a first-class EJS variable. Use the built-in locals object for safe access with fallbacks:
<!-- After: clean, idiomatic EJS -->
<title><%= locals.title || 'My App' %></title>Three ways to set locals
From actions — the most common pattern:
return {
page: 'blog/show',
props: { post },
locals: {
title: `${post.title} | My Blog`,
description: post.excerpt,
ogImage: post.coverImageUrl
}
}From hooks/middleware — for request-scoped values (concurrency-safe via AsyncLocalStorage):
sails.inertia.local('canonicalUrl', `https://myapp.com${req.path}`)Globally — for app-wide defaults set once at boot:
sails.inertia.localGlobally('title', 'My App')
sails.inertia.localGlobally('ogImage', '/images/default-og.png')Locals merge in precedence order: global → request-scoped → action (last wins).
Migration guide
-
Update inertia-sails
npm install inertia-sails@1.2.0
-
Rename
viewDatatolocalsin action returnsFind and replace
viewData:withlocals:in your controllers. -
Rename API calls
Find Replace with sails.inertia.viewData(sails.inertia.local(sails.inertia.viewDataGlobally(sails.inertia.localGlobally(sails.inertia.getViewData(sails.inertia.getLocals( -
Update
views/app.ejsReplace
viewData.xreferences withlocals.x:<!-- Before --> <title><%= typeof viewData !== 'undefined' && viewData.title ? viewData.title : 'My App' %></title> <meta name="description" content="<%= typeof viewData !== 'undefined' && viewData.description ? viewData.description : '' %>" /> <!-- After --> <title><%= locals.title || 'My App' %></title> <meta name="description" content="<%= locals.description || '' %>" />
Breaking changes
viewDatakey in action returns no longer recognized — uselocalssails.inertia.viewData()removed — usesails.inertia.local()sails.inertia.viewDataGlobally()removed — usesails.inertia.localGlobally()sails.inertia.getViewData()removed — usesails.inertia.getLocals()viewDatais no longer passed as a nested object tores.view()— locals are spread directly
Documentation
- Locals (inertia-sails) — full API reference, precedence rules, real-world examples
- Locals (Boring Stack) — practical guide with quick examples
Full Changelog: v1.1.0...v1.2.0