code-quality/skills/inertia-patterns/SKILL.md
Enforce Inertia.js patterns in Vue components. Prevents axios usage, ensures proper form handling, and guides navigation patterns. Activates when working with data fetching, form submissions, or page navigation in Vue components.
npx skillsauth add richardhowes/self-improvement-code-quality-plugin inertia-patternsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Inertia is the ONLY acceptable way to handle data fetching and navigation in VILT stack.
NEVER import or use:
axiosfetch (for API calls)Inertia handles all server communication.
// WRONG - Will be flagged as CRITICAL issue
import axios from 'axios'
const response = await axios.get('/api/users')
// WRONG
const response = await fetch('/api/users')
// CORRECT - Use Inertia
import { router } from '@inertiajs/vue3'
router.reload({ only: ['users'] })
Data comes from controller props, not API calls:
// Props are passed by the Laravel controller
interface Props {
users: User[]
filters: Filters
}
const props = defineProps<Props>()
// Use props directly
const userCount = computed(() => props.users.length)
Use router.reload() to refresh specific props:
import { router } from '@inertiajs/vue3'
// Reload only 'users' prop
router.reload({ only: ['users'] })
// Reload with new filters
router.reload({
only: ['users'],
data: {
search: searchQuery.value,
status: selectedStatus.value,
},
})
// Reload preserving scroll position
router.reload({
only: ['users'],
preserveScroll: true,
})
Be specific about what data to reload:
// CORRECT: Only reload what changed
router.reload({ only: ['bookings'] })
// AVOID: Full page reload
router.reload() // Reloads all props
Always use useForm() for forms:
import { useForm } from '@inertiajs/vue3'
const form = useForm({
name: '',
email: '',
role: 'user',
})
function submit() {
form.post(route('users.store'), {
preserveScroll: true,
onSuccess: () => {
form.reset()
},
onError: (errors) => {
// Handle validation errors
console.error(errors)
},
})
}
Use appropriate HTTP methods:
// Create
form.post(route('users.store'))
// Update
form.put(route('users.update', user.id))
form.patch(route('users.update', user.id))
// Delete
form.delete(route('users.destroy', user.id))
Access form state properties:
// Processing state (for loading indicators)
<Button :disabled="form.processing">
{{ form.processing ? 'Saving...' : 'Save' }}
</Button>
// Validation errors
<FloatingLabelInput
v-model="form.name"
:error="form.errors.name"
/>
// Check if form has been modified
const hasChanges = computed(() => form.isDirty)
// Reset form
form.reset()
form.reset('name', 'email') // Reset specific fields
// Clear errors
form.clearErrors()
form.clearErrors('name')
Transform data before sending:
const form = useForm({
startDate: null as Date | null,
amount: '',
})
function submit() {
form.transform((data) => ({
...data,
startDate: data.startDate?.toISOString(),
amount: parseFloat(data.amount),
})).post(route('bookings.store'))
}
Use router for navigation:
import { router } from '@inertiajs/vue3'
// Simple visit
router.visit('/users')
// Visit with method
router.visit('/users', { method: 'post', data: {} })
// Replace history (no back)
router.visit('/dashboard', { replace: true })
// Preserve state
router.visit('/users', {
preserveState: true,
preserveScroll: true,
})
Use Inertia's Link for navigation links:
<script setup lang="ts">
import { Link } from '@inertiajs/vue3'
</script>
<template>
<!-- Basic link -->
<Link :href="route('users.index')">Users</Link>
<!-- With method -->
<Link :href="route('logout')" method="post" as="button">
Logout
</Link>
<!-- Preserve scroll -->
<Link :href="route('users.show', user.id)" preserve-scroll>
{{ user.name }}
</Link>
</template>
Access shared data from HandleInertiaRequests middleware:
import { usePage } from '@inertiajs/vue3'
const page = usePage()
// Access auth user
const user = computed(() => page.props.auth.user)
// Access flash messages
const flash = computed(() => page.props.flash)
// Access any shared prop
const appName = computed(() => page.props.appName)
Type the page props:
interface PageProps {
auth: {
user: User | null
}
flash: {
success?: string
error?: string
}
}
const page = usePage<PageProps>()
Handle navigation events:
import { router } from '@inertiajs/vue3'
// Before navigation starts
router.on('before', (event) => {
// Return false to cancel
if (!confirm('Leave page?')) {
return false
}
})
// Navigation started
router.on('start', (event) => {
showLoadingIndicator()
})
// Navigation finished
router.on('finish', (event) => {
hideLoadingIndicator()
})
// Successful response
router.on('success', (event) => {
console.log('Page loaded')
})
// Error response
router.on('error', (errors) => {
console.error(errors)
})
Implement search with debounce:
import { watch, ref } from 'vue'
import { router } from '@inertiajs/vue3'
import { useDebounceFn } from '@vueuse/core'
const search = ref('')
const debouncedSearch = useDebounceFn(() => {
router.reload({
only: ['users'],
data: { search: search.value },
preserveState: true,
})
}, 300)
watch(search, debouncedSearch)
Handle pagination:
function changePage(page: number) {
router.reload({
only: ['users'],
data: { page },
preserveState: true,
preserveScroll: true,
})
}
Handle destructive actions:
function deleteUser(user: User) {
if (confirm(`Delete ${user.name}?`)) {
router.delete(route('users.destroy', user.id), {
preserveScroll: true,
})
}
}
development
Apply VILT stack coding standards when writing Vue, Laravel, or TypeScript code. Automatically enforces patterns for Composition API, Form Requests, Inertia navigation, and component hierarchy. Activates when creating or modifying Vue components, Laravel controllers, services, or models.
testing
Self-improving meta-skill that learns from user corrections and updates the TARGET PROJECT's skill files. Only runs when user explicitly says "reflect". Commits all skill updates to Git.
documentation
Guide component selection in ResRequest Vue projects. Ensures correct usage of ShadCN-Vue vs custom components, proper imports, and design system compliance. Activates when creating or modifying Vue components.
documentation
Fetch GitHub issues, spawn sub-agents to implement fixes and open PRs, then monitor and address PR review comments. Usage: /gh-issues [owner/repo] [--label bug] [--limit 5] [--milestone v1.0] [--assignee @me] [--fork user/repo] [--watch] [--interval 5] [--reviews-only] [--cron] [--dry-run] [--model glm-5] [--notify-channel -1002381931352]