All skills

Inertia.js server-side rendering, redirects, shared data, forms, validation, and client-side patterns for The Boring JavaScript Stack. Use this skill when building pages, handling form submissions, managing redirects, sharing data, or working with any Inertia.js feature in a Sails.js application.

Use this Skill: https://skilld.dev/gh/sailscastshq/boring-stack/inertia

This session only. Nothing lands on disk.

rulesclient-side-components.md

≈1.8k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Client-Side Components

The Link Component

Use Link instead of <a> tags for internal navigation. It makes XHR requests instead of full page loads:

React

import { Link } from '@inertiajs/react'

// Basic link
<Link href="/dashboard">Dashboard</Link>

// With styling
<Link
  href="/signup"
  className="rounded-lg bg-brand px-6 py-2.5 font-semibold text-white"
>
  Get Started
</Link>

// Active link detection
const { url } = usePage()
<Link
  href="/features"
  className={url === '/features' ? 'bg-brand-100 text-brand-700' : 'text-gray-700'}
>
  Features
</Link>

// Method and data
<Link href="/logout" method="delete" as="button">
  Logout
</Link>

Vue

<script setup>
import { Link } from '@inertiajs/vue3'
</script>

<template>
  <Link href="/dashboard">Dashboard</Link>
  <Link href="/signup" class="bg-brand px-6 py-2.5 text-white">Sign up</Link>
  <Link href="/logout" method="delete" as="button">Logout</Link>
</template>

Svelte

<script>
  import { Link } from '@inertiajs/svelte'
</script>

<Link href="/dashboard">Dashboard</Link>
<Link href="/forgot-password" class="text-brand hover:underline">Forgot Password</Link>

When to Use <a> Instead of Link

Use regular <a> tags for:

  • External URLs (https://example.com)
  • OAuth redirect URLs (/auth/google/redirect)
  • File downloads
  • Any URL that should trigger a full page load
// OAuth buttons use <a> tags, not Link
<a href="/auth/google/redirect" className="btn">Sign in with Google</a>
<a href="/auth/github/redirect" className="btn">Sign in with GitHub</a>

The router Object

For programmatic navigation (outside of links and forms):

import { router } from '@inertiajs/react'

// Navigate
router.visit('/dashboard')
router.get('/users')

// Mutations
router.post('/teams', { name: 'New Team' })
router.patch('/teams/1', { name: 'Updated Team' })
router.delete('/logout')

// With options
router.visit('/dashboard', {
  method: 'get',
  preserveScroll: true,
  preserveState: true,
  only: ['users'], // Partial reload
  except: ['notifications'], // Exclude props
  onSuccess: (page) => {},
  onError: (errors) => {}
})

// Reload current page (partial reload)
router.reload({ only: ['notifications'] })

Vue

<script setup>
import { router } from '@inertiajs/vue3'

function logout() {
  router.delete('/logout')
}
</script>

<template>
  <button @click="logout">Logout</button>
  <button @click="router.delete('/logout')">Logout (inline)</button>
</template>

usePage() -- Access Page Data

Access the current page object (URL, props, component) from any component:

React

import { usePage } from '@inertiajs/react'

export default function AppLayout({ children }) {
  const { url, props } = usePage()
  const loggedInUser = props.loggedInUser
  const flash = props.flash
  const errors = props.errors

  return (
    <div>
      {loggedInUser ? (
        <span>{loggedInUser.fullName}</span>
      ) : (
        <Link href="/login">Login</Link>
      )}
      {children}
    </div>
  )
}

Vue

<script setup>
import { usePage } from '@inertiajs/vue3'
import { computed } from 'vue'

const page = usePage()
const loggedInUser = computed(() => page.props.loggedInUser)
</script>

<template>
  <span v-if="loggedInUser">{{ loggedInUser.fullName }}</span>
  <Link v-else href="/login">Login</Link>
</template>

Head Component -- Page Title and Meta

Set the page title and meta tags:

React

import { Head } from '@inertiajs/react'

export default function Login() {
  return (
    <>
      <Head title="Login | My App" />
      {/* page content */}
    </>
  )
}

// With meta tags
;<Head>
  <title>Dashboard</title>
  <meta name="description" content="Your dashboard" />
</Head>

Vue

<template>
  <Head title="Login | My App" />
  <!-- page content -->
</template>

Svelte

<svelte:head>
  <title>Login | Mellow</title>
</svelte:head>

Persistent Layouts

Layouts wrap page components and persist across navigation (they don't remount). This preserves state like scroll position, audio players, etc.

React

// Define layout on the page component
import DashboardLayout from '@/layouts/DashboardLayout'

ProfileSettings.layout = (page) => (
  <DashboardLayout title="Profile" maxWidth="narrow">
    {page}
  </DashboardLayout>
)

export default function ProfileSettings() {
  return <div>Profile content</div>
}

Vue

<script setup>
import DashboardLayout from '@/layouts/DashboardLayout.vue'

defineOptions({
  layout: DashboardLayout
})

// Or with props:
defineOptions({
  layout: (h, page) =>
    h(DashboardLayout, { maxWidth: 'narrow', title: 'Profile' }, () => page)
})
</script>

Nested Layouts

// Page with two levels of layout
ProfileSettings.layout = (page) => (
  <DashboardLayout title="Profile">
    <SettingsLayout>{page}</SettingsLayout>
  </DashboardLayout>
)

Layout Component Example

// assets/js/layouts/AppLayout.jsx
import { Link, usePage } from '@inertiajs/react'
import { Toast } from 'primereact/toast'
import { useFlashToast } from '@/hooks/useFlashToast'
import { useRef } from 'react'

export default function AppLayout({ children }) {
  const { loggedInUser } = usePage().props
  const { url } = usePage()
  const toast = useRef(null)

  useFlashToast(toast)

  return (
    <div className="flex min-h-screen flex-col">
      <header>
        <nav className="flex items-center justify-between px-4 py-4">
          <Link href="/">
            <img src="/images/logo.svg" alt="Logo" />
          </Link>

          {loggedInUser ? (
            <div className="flex items-center space-x-6">
              <Link href="/dashboard">Dashboard</Link>
              <Link href="/profile">{loggedInUser.fullName}</Link>
            </div>
          ) : (
            <div className="flex items-center space-x-3">
              <Link href="/login">Login</Link>
              <Link
                href="/signup"
                className="bg-brand text-white px-4 py-2 rounded"
              >
                Sign up
              </Link>
            </div>
          )}
        </nav>
      </header>

      <main className="flex-1">{children}</main>
      <Toast ref={toast} />
    </div>
  )
}

Remembering State

Preserve component state across navigation:

import { useRemember } from '@inertiajs/react'

// State persists when navigating away and back
const [filters, setFilters] = useRemember(
  { search: '', status: 'all' },
  'user-filters'
)
<script setup>
import { useRemember } from '@inertiajs/vue3'
const filters = useRemember({ search: '', status: 'all' }, 'user-filters')
</script>

Source: SKILL.md on GitHub

3 warnings17d4 checks · Risk SAFE
  • Gen Agent Trust Hub17d

    This skill provides comprehensive documentation and guidelines for integrating Inertia.js within The Boring JavaScript Stack (Sails.js). No security vulnerabilities or malicious patterns were detected.

  • Socket17d

    1 alert: gptAnomaly

  • Snyk17d

    Risk: MEDIUM · 1 issue

  • Runlayer7mo

    16/16 files flagged

Signed by skilld at fb2b7e0. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 3 days ago.

Activeupdated 7 months ago
Other metadata
metadata
{
  "author": "sailscastshq",
  "version": "1.0.0",
  "tags": "inertia, sails, boring-stack, spa, forms, redirects, shared-data, props"
}

README badge

README badge for sailscastshq/boring-stack/inertia