Inertia.js: bridge entre Laravel e frontend moderno

1. Introdução ao Inertia.js e seu Papel no Ecossistema PHP

Inertia.js é uma abordagem inovadora para construir aplicações web modernas sem a complexidade de uma API REST dedicada. Diferente do Livewire, que mantém todo o estado no servidor, ou de frameworks como Vue e React puros que exigem APIs completas, Inertia atua como uma ponte entre o Laravel e o frontend, permitindo criar Single Page Applications (SPAs) de forma simplificada.

O conceito central é que você continua escrevendo seus controladores e rotas no Laravel, mas em vez de retornar views Blade, você retorna componentes frontend. A mágica acontece porque Inertia intercepta as requisições, serializa os dados e os envia como JSON para o frontend, que renderiza os componentes sem recarregar a página.

Para desenvolvedores PHP que trabalham com Laravel, Inertia oferece o melhor dos dois mundos: a produtividade do ecossistema Laravel com a experiência de usuário de uma SPA moderna.

2. Arquitetura e Funcionamento Interno

O fluxo de uma requisição Inertia começa no navegador, que faz uma requisição HTTP normal para uma rota Laravel. O Laravel processa a requisição normalmente, mas em vez de retornar uma view Blade completa, o controlador utiliza Inertia::render():

<?php

namespace App\Http\Controllers;

use Inertia\Inertia;
use App\Models\User;

class UserController extends Controller
{
    public function index()
    {
        $users = User::all();

        return Inertia::render('Users/Index', [
            'users' => $users
        ]);
    }
}

O papel do Inertia::render() é crucial: ele retorna uma resposta JSON contendo o nome do componente e os dados (props). No frontend, o adaptador Inertia (Vue, React ou Svelte) interpreta essa resposta e renderiza o componente correspondente, mantendo o estado da aplicação sem recarregar a página.

O gerenciamento de estado no frontend acontece sem a necessidade de uma API REST explícita. Cada requisição subsequente (via <Link> ou Inertia.visit()) segue o mesmo padrão: o Laravel processa, Inertia serializa, e o frontend atualiza apenas a parte necessária da interface.

3. Configuração Inicial no Laravel

Para começar, instale o pacote Inertia no Laravel:

composer require inertiajs/inertia-laravel

Em seguida, instale o adaptador frontend de sua escolha. Para Vue 3:

npm install @inertiajs/vue3

Crie o middleware HandleInertiaRequests:

php artisan inertia:middleware

Registre o middleware no app/Http/Kernel.php:

protected $middlewareGroups = [
    'web' => [
        // ... outros middlewares
        \App\Http\Middleware\HandleInertiaRequests::class,
    ],
];

Configure o arquivo resources/views/app.blade.php como shell inicial:

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    @vite('resources/js/app.js')
    @inertiaHead
</head>
<body>
    @inertia
</body>
</html>

4. Roteamento e Compartilhamento de Dados

As rotas no Laravel permanecem praticamente as mesmas. A diferença está no retorno dos controladores:

<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\DashboardController;

Route::middleware(['auth'])->group(function () {
    Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard');
});

Para compartilhar dados globais (como o usuário autenticado), utilize o método Share() no middleware:

<?php

namespace App\Http\Middleware;

use Illuminate\Http\Request;
use Inertia\Middleware;

class HandleInertiaRequests extends Middleware
{
    public function share(Request $request): array
    {
        return array_merge(parent::share($request), [
            'auth' => [
                'user' => $request->user() ? [
                    'id' => $request->user()->id,
                    'name' => $request->user()->name,
                    'email' => $request->user()->email,
                ] : null,
            ],
            'flash' => [
                'success' => session('success'),
                'error' => session('error'),
            ],
        ]);
    }
}

Dados específicos por requisição são passados como props no controlador:

<?php

namespace App\Http\Controllers;

use Inertia\Inertia;
use App\Models\Post;

class PostController extends Controller
{
    public function show(Post $post)
    {
        return Inertia::render('Posts/Show', [
            'post' => $post->load('author'),
            'relatedPosts' => Post::where('category_id', $post->category_id)
                ->where('id', '!=', $post->id)
                ->limit(3)
                ->get()
        ]);
    }
}

5. Componentes e Navegação no Frontend

No frontend, crie componentes Vue que recebem props tipadas. Exemplo de resources/js/Pages/Posts/Show.vue:

<template>
    <Layout>
        <h1>{{ post.title }}</h1>
        <p>{{ post.content }}</p>

        <h2>Posts Relacionados</h2>
        <ul>
            <li v-for="related in relatedPosts" :key="related.id">
                <Link :href="`/posts/${related.id}`">{{ related.title }}</Link>
            </li>
        </ul>
    </Layout>
</template>

<script setup>
import { Link } from '@inertiajs/vue3';
import Layout from '@/Components/Layout.vue';

defineProps({
    post: Object,
    relatedPosts: Array
});
</script>

Para navegação sem recarregar a página, use o componente <Link>:

<template>
    <Link href="/dashboard" class="nav-link">
        Dashboard
    </Link>
</template>

Gerenciamento de formulários com Inertia.post():

<template>
    <form @submit.prevent="submit">
        <input v-model="form.title" placeholder="Título" />
        <div v-if="form.errors.title">{{ form.errors.title }}</div>

        <button type="submit" :disabled="form.processing">
            Salvar
        </button>
    </form>
</template>

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

const form = useForm({
    title: ''
});

function submit() {
    form.post('/posts');
}
</script>

6. Tratamento de Erros e Validação

O Laravel compartilha automaticamente os erros de validação através do objeto $errors:

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class StorePostRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'title' => 'required|string|max:255',
            'content' => 'required|string',
        ];
    }
}

No frontend, os erros ficam disponíveis no objeto form.errors ou através do componente Errors:

<template>
    <div v-if="$page.props.errors.title" class="error">
        {{ $page.props.errors.title[0] }}
    </div>
</template>

Para flash messages, utilize a sessão compartilhada no middleware:

<?php

namespace App\Http\Controllers;

use Inertia\Inertia;

class PostController extends Controller
{
    public function store(StorePostRequest $request)
    {
        Post::create($request->validated());

        return redirect()->back()->with('success', 'Post criado com sucesso!');
    }
}

No frontend, acesse a flash message:

<template>
    <div v-if="$page.props.flash.success" class="alert alert-success">
        {{ $page.props.flash.success }}
    </div>
</template>

7. Boas Práticas e Integração com Outros Pacotes

Organize seus diretórios separando páginas e componentes:

resources/js/
├── Pages/
│   ├── Dashboard.vue
│   ├── Posts/
│   │   ├── Index.vue
│   │   └── Show.vue
│   └── Users/
│       └── Index.vue
├── Components/
│   ├── Layout.vue
│   ├── Button.vue
│   └── Modal.vue
└── app.js

Para depuração, utilize o Laravel Telescope que captura requisições Inertia perfeitamente:

composer require laravel/telescope

Combine Inertia com Rate Limiting quando necessário:

Route::middleware(['throttle:10,1'])->group(function () {
    Route::post('/posts', [PostController::class, 'store']);
});

8. Limitações e Quando Evitar o Inertia.js

Inertia não é a melhor escolha para todos os cenários. Evite-o quando:

  • Aplicações com muitas interações em tempo real: Se sua aplicação necessita de WebSockets constantes, uma API REST com WebSockets dedicados pode ser mais eficiente.
  • Aplicações mobile ou third-party: Se você precisa expor uma API para clientes externos, uma API REST tradicional é obrigatória.
  • Performance crítica: Em aplicações com milhares de componentes simultâneos, o overhead de serialização do Inertia pode impactar a performance.
  • Migração gradual: Se você precisa migrar um Laravel tradicional para Inertia, faça-o de forma incremental, começando por páginas menos críticas.

Para projetos novos que se encaixam no perfil de CRUD tradicional com boa experiência de usuário, Inertia.js é uma escolha excelente que combina a robustez do Laravel com a fluidez de uma SPA moderna.

Referências