Skip to content

The Boring Stack 1.5.0

Latest

Choose a tag to compare

@DominusKelvin DominusKelvin released this 28 May 09:49
· 3 commits to main since this release

The Boring Stack 1.5.0

The Boring Stack 1.5.0 is the release that moves the Sails + Inertia foundation to stable Inertia v3 and rounds out the developer experience around rendering, validation, testing, and errors.

This release includes inertia-sails@1.5.0, rsbuild-plugin-inertia@0.0.1, updated templates, selective SSR, Precognition, richer v3 protocol metadata, and Youch-powered development errors.

Highlights

  • Stable Inertia v3 support across inertia-sails and the Vue, React, and Svelte templates.
  • New rsbuild-plugin-inertia package for Rsbuild page resolution, lazy page splitting, optional Axios adapter stubbing, and SSR build wiring.
  • Selective Inertia SSR that runs inside Sails without a separate SSR server process.
  • Inertia Precognition support for live server-side validation using the same Action2 inputs and badRequest flow.
  • Rescuable deferred props with rescuedProps, plus the .rescue() and { rescue: true } APIs.
  • sails.inertia.preserveFragment() for redirects that should keep the current hash.
  • Production Inertia error pages for 403, 404, 500, and 503.
  • Youch-powered development error pages that Inertia can show inside its development modal.
  • Template test structure moved toward Sounding-first functional testing.
  • Templates updated to Shipwright ^1.4.0, Inertia adapters ^3.1.1, and inertia-sails@^1.5.0.

Inertia v3 support

The root page payload now follows Inertia v3's dedicated JSON script shape:

<div id="app"></div>
<script type="application/json" data-page="app">
  <%- JSON.stringify(page).replace(/</g, '\\u003c') %>
</script>

The adapter now emits the protocol metadata the v3 client adapters need:

  • sharedProps
  • clearHistory
  • encryptHistory
  • deferredProps
  • mergeProps
  • prependProps
  • deepMergeProps
  • matchPropsOn
  • scrollProps
  • rescuedProps
  • preserveFragment

This keeps Sails actions simple while giving the Inertia client the full v3 page object shape.

Rescuable deferred props

Deferred props can now fail gracefully when the data is useful but not critical:

analytics: sails.inertia
  .defer(async () => {
    return await Analytics.getExpensiveReport()
  })
  .rescue()

You can also pass the intent inline:

analytics: sails.inertia.defer(
  async () => {
    return await Analytics.getExpensiveReport()
  },
  { rescue: true }
)

When a rescuable deferred prop fails, inertia-sails omits it from props and reports the key in rescuedProps. Use this for secondary panels such as analytics, recommendations, and activity feeds. Do not use it for critical page data.

Preserved fragments

Use preserveFragment() when a redirect should carry the current hash:

sails.inertia.preserveFragment()
return '/articles/new-slug'

This is opt-in because fragment preservation should be a redirect-level product decision, not hidden global behavior.

rsbuild-plugin-inertia

rsbuild-plugin-inertia centralizes Inertia-specific Rsbuild behavior:

  • default page resolver injection for ./pages
  • an Inertia/Vite-style pages shorthand for non-standard directories
  • lazy page loading by default for page-level code splitting
  • lazy: false for single-bundle apps
  • Vue, React, and Svelte adapter detection
  • automatic SSR build environment setup when assets/js/ssr.js exists
  • optional Axios adapter stubbing when Axios is not installed

Template config/shipwright.js files now use this shape:

const { pluginVue } = require('@rsbuild/plugin-vue')
const { pluginInertia } = require('rsbuild-plugin-inertia')

module.exports.shipwright = {
  build: {
    plugins: [pluginVue(), pluginInertia()]
  }
}

Use pluginReact() or pluginSvelte() for React and Svelte apps.

Because the plugin stubs Inertia v3's optional Axios adapter import when Axios is not installed, templates no longer need Axios unless the app code imports Axios directly.

Selective SSR

The Boring Stack now supports Inertia SSR without requiring a separate SSR server process. Shipwright builds a private SSR bundle, and inertia-sails imports it in-process from Sails.

Enable SSR for every Inertia page:

module.exports.inertia = {
  ssr: true
}

Or enable it selectively:

module.exports.inertia = {
  ssr: {
    enabled: true,
    pages: ['index', 'pricing', 'blog/show']
  }
}

Opt out per response:

return {
  page: 'dashboard/index',
  ssr: false,
  props: { user }
}

Use SSR for public pages where first paint, SEO, and social sharing matter. Skip it for most authenticated dashboards and heavily browser-dependent screens.

Precognition

Precognition comes from the Laravel/Inertia ecosystem. It lets the client ask the same server action to validate data before the real submit happens.

That means you do not duplicate server validation in the browser.

Client:

const form = useForm({
  email: ''
}).withPrecognition('post', '/forgot-password')

form.validate('email')

Server response:

// api/responses/precognitionSuccess.js
module.exports = function precognitionSuccess() {
  return this.req._sails.inertia.handlePrecognitionSuccess(this.req, this.res)
}

Server action:

if (sails.inertia.isPrecognitive(this.req)) {
  return exits.precognitionSuccess()
}

For availability checks such as "username is taken", use shouldValidate() so a blur on one field does not run every expensive validation rule:

if (sails.inertia.shouldValidate('username', this.req)) {
  const exists = await User.count({ username })

  if (exists > 0) {
    throw {
      badSignupRequest: {
        problems: [{ username: 'Username is already taken.' }]
      }
    }
  }
}

Precognition is best for signup, forgot password, invite, username, profile, onboarding, and billing forms where early server-backed feedback improves the product.

Humanized validation errors

inertia-sails now humanizes common Action2, Anchor, and RTTC validation messages before they reach Inertia forms. Precognition and normal submit-time errors share the same path, so the user sees consistent field messages whether validation happened on blur or on submit.

Rich development errors with Youch

Development server errors now render with Youch.

For Inertia requests, Inertia shows the Youch HTML in its development error modal. For normal browser visits, the browser gets the full Youch page.

The Youch output includes readable stack frames, source snippets, request context, and sanitized metadata. Cookies, authorization headers, CSRF tokens, passwords, secrets, and session-looking values are redacted before rendering.

We also opened an upstream Inertia issue for the tiny empty modal flash that can appear before iframe content is written: inertiajs/inertia#3130.

Production Inertia error pages

Templates now ship an error page:

  • assets/js/pages/error.vue
  • assets/js/pages/error.jsx
  • assets/js/pages/error.svelte

inertia-sails renders that page by default for:

  • 403
  • 404
  • 500
  • 503

The page receives:

{
  status: 404,
  title: 'Page not found',
  message: 'The page you are looking for could not be found.'
}

Templates also include notFound and forbidden responses that delegate to sails.inertia.handleErrorPage().

Hybrid apps can keep EJS error pages by setting:

module.exports.inertia = {
  errorPage: false
}

Sounding and template tests

The templates now lean into Sounding as the default testing story:

  • unit tests for small pure logic
  • functional tests for Sails actions, responses, auth, mail, and Inertia responses
  • browser tests only when browser behavior itself matters

This gives Boring Stack apps a calmer test split and avoids making every workflow a full browser automation problem.

Upgrade guide

1. Update packages

Vue:

npm install inertia-sails@^1.5.0 @inertiajs/vue3@^3.1.1 rsbuild-plugin-inertia@latest sails-hook-shipwright@^1.4.0
npm uninstall axios

React:

npm install inertia-sails@^1.5.0 @inertiajs/react@^3.1.1 rsbuild-plugin-inertia@latest sails-hook-shipwright@^1.4.0
npm uninstall axios

Svelte:

npm install inertia-sails@^1.5.0 @inertiajs/svelte@^3.1.1 rsbuild-plugin-inertia@latest sails-hook-shipwright@^1.4.0
npm uninstall axios

Keep Axios only if your app imports Axios directly.

2. Update views/app.ejs

Move the page payload to the dedicated JSON script:

<div id="app"></div>
<script type="application/json" data-page="app">
  <%- JSON.stringify(page).replace(/</g, '\\u003c') %>
</script>

If you enable SSR, render ssr.body when present and include ssr.head in the document head.

3. Update config/shipwright.js

Add pluginInertia() after the framework plugin:

const { pluginVue } = require('@rsbuild/plugin-vue')
const { pluginInertia } = require('rsbuild-plugin-inertia')

module.exports.shipwright = {
  build: {
    plugins: [pluginVue(), pluginInertia()]
  }
}

4. Simplify assets/js/app.js

If your pages live in assets/js/pages, remove custom page resolution and let rsbuild-plugin-inertia inject the default resolver:

createInertiaApp({
  setup({ el, App, props, plugin }) {
    createApp({ render: () => h(App, props) }).use(plugin).mount(el)
  }
})

Use pages: './screens' or the object form only when your app has a non-standard page directory.

5. Add production error pages

Create an error page under your page directory and add notFound and forbidden responses that call sails.inertia.handleErrorPage().

6. Add Precognition where it helps

Add api/responses/precognitionSuccess.js, return that exit before side effects, and call .withPrecognition() on the client form.

7. Add SSR only where it pays for itself

Create assets/js/ssr.js, enable ssr: true or selective ssr.pages, and verify view-source: contains rendered HTML for SSR-enabled pages.

8. Use the new protocol APIs intentionally

  • Use sails.inertia.preserveFragment() only for redirects where the hash should survive.
  • Use { rescue: true } or .rescue() only for secondary deferred props.
  • Use shouldValidate() for expensive Precognition checks.

9. Verify each app

Run:

npm install
npm run lint
npm test
npm run dev

Then manually check:

  • the root view includes data-page="app"
  • Inertia navigation still works
  • normal submit validation still appears
  • Precognition validation appears on blur where enabled
  • 404 and 403 render the production error page
  • development server errors show Youch
  • SSR pages show full HTML in view-source
  • EJS pages still work in hybrid apps

PRs included

  • #199 Support Inertia v3 protocol metadata
  • #201 Complete Sounding integration across templates
  • #202 Update templates to Shipwright 1.3.0
  • #203 Add selective Inertia SSR support
  • #204 Add Inertia Precognition support
  • #205 Add rich Boring Stack error handling

Compare: v1.2.3...v1.5.0