Saltar al contenido principal

Vue 3 + Inertia.js en 2026: la combinación que hace que los SaaS sean entregables

Autor
Ignacio AmatIgnacio Amat
Publicado
Lectura5 min
Laravel, Inertia.js y Vue 3 trabajando juntos en un SaaS

Llevo años construyendo SaaS con Laravel y he usado de todo: Blade clásico, SPAs con Vue Router + API REST, Livewire. Desde que adopté Inertia.js con Vue 3, mi velocidad de entrega se duplicó. No es exageración.

No necesitas una API separada. No necesitas Vue Router. No necesitas sincronizar tipos entre backend y frontend. El túnel de datos entre Laravel y Vue desaparece, y te quedas solo con lo que importa: construir features.


Cómo funciona el flujo de datos (y por qué importa)

En una SPA tradicional, cada pantalla requiere:

  1. Controlador Laravel → API Resource → JSON → Axios → Store Vue → Componente → Render.

En Inertia:

  1. Controlador Laravel → Inertia::render() → Componente Vue + props → Render.

El SSR (Server-Side Rendering) de Inertia hace que los motores de búsqueda vean HTML completo, y el usuario disfruta de navegación instantánea tipo SPA. Sin perder SEO, sin sacrificar UX.

// app/Http/Controllers/DashboardController.php
class DashboardController extends Controller
{
    public function __invoke(): Response
    {
        $user = auth()->user();
  
        return Inertia::render('Dashboard/Index', [
            'stats' => [
                'total_revenue' => (float) Revenue::totalForUser($user),
                'active_subscriptions' => $user->subscriptions()->active()->count(),
                'pending_invoices' => $user->invoices()->pending()->count(),
            ],
            'recentActivity' => Activity::forUser($user)
                ->take(10)
                ->get()
                ->through(fn ($a) => [
                    'id' => $a->id,
                    'action' => $a->action,
                    'description' => $a->description,
                    'created_at' => $a->created_at->diffForHumans(),
                ]),
            'filters' => request()->only(['period', 'status']),
        ]);
    }
}

Sin Resources, sin transformaciones manuales en el frontend. Los datos llegan como props tipadas.


Lo que realmente acelera: patrones que funcionan

1. Formularios sin librería externa

No necesitas FormKit, VeeValidate ni Zod. Con @inertiajs/inertia y useForm:

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

const form = useForm({
  name: '',
  email: '',
  plan: 'monthly',
  terms: false,
});

function submit() {
  form.post('/subscribe', {
    preserveScroll: true,
    onSuccess: () => form.reset(),
    onError: (errors) => console.table(errors),
  });
}
</script>

<template>
  <form @submit.prevent="submit">
    <TextInput v-model="form.name" :error="form.errors.name" />
    <TextInput v-model="form.email" type="email" :error="form.errors.email" />
    <PrimaryButton :disabled="form.processing">
      {{ form.processing ? 'Processing...' : 'Subscribe' }}
    </PrimaryButton>
  </form>
</template>

form.processing evita doble envío. form.errors mapea automáticamente los errores de validación de Laravel. preserveScroll mantiene la posición al recargar.

2. Autorización sin duplicar lógica

// app/Http/Controllers/InvoicesController.php
class InvoicesController extends Controller
{
    public function index(): Response
    {
        $this->authorize('viewAny', Invoice::class);

        return Inertia::render('Invoices/Index', [
            'invoices' => Invoice::forUser(auth()->user())
                ->with('items')
                ->latest()
                ->paginate(15)
                ->through(fn ($invoice) => [
                    'id' => $invoice->id,
                    'total' => $invoice->total_cents / 100,
                    'status' => $invoice->status,
                    'can' => [
                        'pay' => auth()->user()->can('pay', $invoice),
                        'download' => auth()->user()->can('download', $invoice),
                        'cancel' => auth()->user()->can('cancel', $invoice),
                    ],
                ]),
        ]);
    }
}

En el frontend, renderizas condicionalmente según can:

<button v-if="invoice.can.pay" @click="pay(invoice.id)">Pay Now</button>

Sin tener que replicar policies en JavaScript. Las reglas viven en Laravel, se consumen como props.

3. SSR real para SEO

Inertia SSR no es un “modo compatibilidad”. Es SSR nativo con Node:

npm install @inertiajs/server
node artisan inertia:start-ssr

Los crawlers reciben HTML completo con meta tags, títulos, descripciones y contenido. Tus páginas públicas (landing, docs, pricing) se indexan como cualquier web tradicional, mientras los usuarios logueados navegan con la fluidez de una SPA.


Testing: pest e inertia-testing

// tests/Feature/Http/Controllers/DashboardControllerTest.php
use Inertia\Testing\AssertableInertia;

test('dashboard shows correct stats for authenticated user', function () {
    $user = User::factory()->create();
    Subscription::factory()->for($user)->active()->create(['plan' => 'pro']);
  
    $response = $this->actingAs($user)->get('/dashboard');
  
    $response->assertInertia(fn (AssertableInertia $page) => $page
        ->component('Dashboard/Index')
        ->has('stats')
        ->where('stats.active_subscriptions', 1)
        ->has('recentActivity')
        ->where('filters', [])
    );
});

test('dashboard redirects guests to login', function () {
    $response = $this->get('/dashboard');
    $response->assertRedirect('/login');
});

test('dashboard paginates recent activity', function () {
    $user = User::factory()->create();
    Activity::factory()->for($user)->count(25)->create();
  
    $response = $this->actingAs($user)->get('/dashboard?page=2');
  
    $response->assertInertia(fn (AssertableInertia $page) => $page
        ->component('Dashboard/Index')
        ->has('recentActivity.data', 10) // 10 per page
        ->where('recentActivity.current_page', 2)
    );
});

Sin necesidad de mockear Axios, store de Vue ni llamadas API. El test habla directamente con la página renderizada y verifica las props.


Cuándo NO usar Inertia (con honestidad)

Inertia es mi opción por defecto, pero no es siempre la correcta:

  • Equipos frontend y backend separados: si el frontend lo gestiona un equipo que no toca PHP, mejor una API tradicional con Vue Router.
  • Offline-first / PWA agresivo: Inertia depende del servidor para navegar. Para apps que deben funcionar sin conexión, mejor SPA con SW.
  • Múltiples clientes (web + mobile): si necesitas API para iOS/Android además de web, plantéate si merece mantener dos caminos o unificas en API.
  • Tiempo real intensivo: WebSockets con Inertia funcionan, pero si tu app es 90% tiempo real (como un chat o dashboard tipo trading), una SPA con WS nativo da más control.

Para todo lo demás (SaaS, paneles de administración, herramientas internas, MVPs, landing con SSR), Inertia es imbatible.


Conclusión

Laravel + Inertia + Vue 3 no es una moda: es la forma más productiva que conozco de construir aplicaciones web completas sin duplicar lógica backend/frontend.

Si tu equipo usa Laravel y estás considerando cómo estructurar el frontend, mi recomendación es clara: prueba Inertia con un feature real (no un tutorial). Verás cómo desaparece la fricción entre backend y frontend.


¿Necesitas a alguien que ya domine este stack?

Llevo años construyendo SaaS completos con Laravel, Inertia y Vue 3, desde la base de datos hasta el deploy. Si tu equipo necesita un perfil senior que entregue rápido sin acumular deuda técnica:

  • Stack: Laravel 11, PHP 8.3+, Vue 3 + TS + Inertia, Pest, Vite, Docker, GitHub Actions.
  • Modalidad: EOR (Deel, Remote, Oyster), freelance B2B, o contrato directo en España.
  • Disponibilidad: remoto España/UE/US, proyectos de 3+ meses o rol indefinido.

Ver mi perfil completo y condiciones →


Artículos relacionados

Artículos relacionados

Revisa mi perfil como desarrollador

Si este artículo encaja con los retos técnicos de tu equipo, revisa mi stack o mi disponibilidad profesional.

Cuéntame qué necesitas

Puedes escribirme por un rol, contrato, colaboración técnica, una duda o una consulta general. Con 2-3 líneas de contexto suelo poder responder en menos de 24 horas hábiles.

0/500
Disponibilidad