<template>
<form @submit.prevent="form.post(route('posts.store'))">
<div>
<label>Title</label>
<input v-model="form.title" type="text" />
<div v-if="form.errors.title">{{ form.errors.title }}</div>
</div>
<div>
<label>Body</label>
<textarea v-model="form.body"></textarea>
<div v-if="form.errors.body">{{ form.errors.body }}</div>
</div>
<button type="submit" :disabled="form.processing">
{{ form.processing ? 'Saving...' : 'Save Post' }}
</button>
<div v-if="form.progress">
Upload progress: {{ form.progress.percentage }}%
</div>
</form>
</template>
<script setup>
import { useForm } from '@inertiajs/vue3'
const form = useForm({
title: '',
body: '',
})
</script>
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Http\Request;
use Inertia\Inertia;
class PostController extends Controller
{
public function index()
{
return Inertia::render('Posts/Index', [
'posts' => Post::query()
->with('author:id,name')
->select('id', 'title', 'excerpt', 'user_id', 'published_at')
->latest()
->paginate(20),
// Lazy evaluation - only loads when requested
'filters' => fn () => request()->only(['search', 'category']),
]);
}
public function show(Post $post)
{
$post->load(['author', 'comments.author']);
return Inertia::render('Posts/Show', [
'post' => $post,
'can' => [
'update' => auth()->user()?->can('update', $post),
'delete' => auth()->user()?->can('delete', $post),
],
]);
}
public function store(Request $request)
{
$validated = $request->validate([
'title' => 'required|max:255',
'body' => 'required',
]);
$post = $request->user()->posts()->create($validated);
return redirect()
->route('posts.show', $post)
->with('success', 'Post created!');
}
}
<template>
<div>
<h1>Posts</h1>
<div v-for="post in posts.data" :key="post.id">
<Link :href="route('posts.show', post.id)">
<h2>{{ post.title }}</h2>
</Link>
<p>{{ post.excerpt }}</p>
<small>By {{ post.author.name }}</small>
</div>
<Pagination :links="posts.links" />
</div>
</template>
<script setup>
import { Link } from '@inertiajs/vue3'
import Pagination from '@/Components/Pagination.vue'
defineProps({
posts: Object,
})
</script>
Inertia.js bridges Laravel backends with Vue, React, or Svelte frontends without building a separate API. Server-side controllers return Inertia responses containing component names and props. The frontend renders those components with reactive frameworks. Navigation uses Inertia's router for client-side transitions without full page loads. Shared data like auth user and flash messages pass via middleware. Form submissions use Inertia's form helpers with automatic validation error handling. Lazy data evaluation defers expensive computations until needed. Partial reloads fetch only changed data. This architecture provides SPA experience while maintaining Laravel's routing and controller patterns. No API routes, no state management—just modern frontends with Laravel backends.
Related snips
<?php
namespace App\Providers;
use App\Contracts\PaymentGateway;
use App\Services\StripePaymentGateway;
Laravel service container and dependency injection
import axios, { AxiosError } from 'axios'
import { v4 as uuidv4 } from 'uuid'
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000/api/v1',
timeout: 15000,
Axios API client with interceptors
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
interface FilterState {
search: string
category: string | null
Zustand for lightweight state management
{
"private": true,
"scripts": {
"dev": "vite",
"build": "vite build"
},
Laravel mix/Vite for asset compilation
import React from "react";
type FallbackProps = {
error: Error;
reset: () => void;
};
React Error Boundary + error reporting hook
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
openAnalyzer: true,
});
/** @type {import('next').NextConfig} */
Next.js bundle analyzer for targeted performance work
Share this code
Here's the card — post it anywhere.