Saltar al contenido principal

¿Qué es Vue.js?

Vue.js (en adelante Vue) es un framework JavaScript progresivo para construir interfaces de usuario. “Progresivo” significa que puedes empezar por partes pequeñas y añadir funcionalidades a medida que las necesitas, sirviendo tanto para incrustar en páginas HTML existentes como para construir SPAs a gran escala. El núcleo de Vue es la reactividad. Cuando cambian los datos, el DOM se actualiza automáticamente, así que el desarrollador no tiene que gestionar manualmente “cuándo y qué elemento actualizar”.
En esta página se explica la combinación de Vue 3 e Inertia v3. Los starter kits de Laravel 13 usan esta configuración por defecto.

Options API y Composition API

Vue 3 ofrece dos estilos para escribir componentes: Options API y Composition API. El Options API es el estilo tradicional que viene desde Vue 2. Se define el componente con objetos-opción como data, methods, computed, mounted, etc.
<!-- Ejemplo con Options API -->
<script>
export default {
    data() {
        return { count: 0 }
    },
    methods: {
        increment() {
            this.count++
        }
    }
}
</script>

<template>
    <button @click="increment">{{ count }}</button>
</template>
El Composition API es el nuevo estilo introducido en Vue 3. Combinándolo con la sintaxis <script setup>, se escribe de forma más concisa. Tiene alta reutilización lógica y muy buena compatibilidad con TypeScript.
<!-- Ejemplo con Composition API (<script setup>) -->
<script setup>
import { ref } from 'vue'

const count = ref(0)

function increment() {
    count.value++
}
</script>

<template>
    <button @click="increment">{{ count }}</button>
</template>
En los starter kits de Inertia × Laravel, el estándar es el estilo Composition API con <script setup>. Todos los ejemplos de esta página también están en <script setup>.

Posición dentro de Laravel

Historia

La relación entre Vue y Laravel es antigua; empezó en Laravel 5.3 (2016), cuando se adoptó Vue como framework de frontend por defecto. En el package.json de entonces se incluía Vue, y venía además un componente de ejemplo resources/js/components/ExampleComponent.vue. En Laravel 6 (2019) se separó el scaffold de autenticación al paquete laravel/ui, y el scaffold de Vue también se movió a ese paquete. Actualmente, el estilo dominante es elegir la configuración Inertia + Vue vía el starter kit de laravel new. Vue es el framework JS más familiar para los usuarios de Laravel, y también hay abundantes recursos de aprendizaje en japonés.

Estilo dominante actual: Inertia × Vue

El uso central de Vue en Laravel actualmente es Inertia × Vue. Inertia permite realizar una arquitectura de “monolito moderno” en la que se pasan datos directamente desde los controladores de Laravel a componentes Vue sin diseñar una API.

Configuración

Vía starter kit (recomendado)

Al iniciar un proyecto nuevo, usar el starter kit es lo más sencillo.
laravel new my-app
Si eliges Vue en el prompt interactivo, se configura automáticamente todo lo siguiente.
  • inertiajs/inertia-laravel (adaptador del lado servidor)
  • @inertiajs/vue3 (adaptador del lado cliente)
  • vue (Vue 3)
  • @vitejs/plugin-vue (plugin de Vite)
  • Middleware HandleInertiaRequests
  • Pantallas de autenticación como login y registro (ya implementadas con Inertia + Vue)

Instalación manual

Si vas a añadirlo a un proyecto existente, instala por separado el lado servidor y el lado cliente.
# Lado servidor (PHP)
composer require inertiajs/inertia-laravel

# Lado cliente (JavaScript)
npm install @inertiajs/vue3 vue
npm install --save-dev @vitejs/plugin-vue
A continuación, añade el plugin de Vue en vite.config.js.
import { defineConfig } from 'vite'
import laravel from 'laravel-vite-plugin'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
    plugins: [
        laravel({
            input: ['resources/css/app.css', 'resources/js/app.js'],
            refresh: true,
        }),
        vue({
            template: {
                transformAssetUrls: {
                    base: null,
                    includeAbsolute: false,
                },
            },
        }),
    ],
})
Arranca la app Inertia en resources/js/app.js.
import { createApp, h } from 'vue'
import { createInertiaApp } from '@inertiajs/vue3'
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'

createInertiaApp({
    resolve: (name) =>
        resolvePageComponent(
            `./pages/${name}.vue`,
            import.meta.glob('./pages/**/*.vue'),
        ),
    setup({ el, App, props, plugin }) {
        createApp({ render: () => h(App, props) })
            .use(plugin)
            .mount(el)
    },
})
Para los detalles de la instalación manual (configuración de la plantilla raíz, registro del middleware, etc.), consulta la documentación oficial de Inertia.

Estructura de directorios

En los starter kits, los componentes de página de Vue se colocan en el directorio resources/js/pages/.
resources/js/
├── app.js             # Punto de entrada de la app Inertia
├── bootstrap.js
├── components/        # Componentes de UI reutilizables
│   ├── NavBar.vue
│   └── ...
├── layouts/           # Componentes de layout
│   ├── AppLayout.vue
│   └── AuthLayout.vue
└── pages/             # Componentes de página de Inertia (corresponden al nombre del controlador)
    ├── Auth/
    │   ├── Login.vue
    │   └── Register.vue
    ├── Dashboard.vue
    └── Posts/
        ├── Index.vue
        ├── Create.vue
        └── Show.vue
Cuando escribes Inertia::render('Posts/Index', [...]), el componente correspondiente es resources/js/pages/Posts/Index.vue.

Sintaxis de plantilla de Vue

Presentamos las directivas de plantilla básicas necesarias para leer y escribir el código del starter kit.

{{ }} — Interpolación de variables

Con las llaves dobles se incrustan en la plantilla valores o expresiones de JavaScript.
<script setup>
const name = 'mundo'
const count = 3
</script>

<template>
    <p>Hola, {{ name }}!</p>
    <p>El doble es {{ count * 2 }}</p>
</template>

v-if — Condicionales

<template>
    <p v-if="isLoggedIn">¡Bienvenido!</p>
    <p v-else-if="role === 'admin'">Sesión iniciada como administrador</p>
    <a v-else href="/login">Iniciar sesión</a>
</template>
Equivale a {#if} de Svelte o al operador ternario de React.

v-for — Renderizado de listas

<template>
    <ul>
        <li v-for="post in posts" :key="post.id">{{ post.title }}</li>
    </ul>
</template>
Para un diff eficiente hay que indicar siempre :key. Equivale a Array.map() de React.

v-model — Binding bidireccional

Con v-model puedes sincronizar bidireccionalmente el valor de un elemento de formulario con una variable reactiva.
<script setup>
import { ref } from 'vue'

const title = ref('')
const agreed = ref(false)
const role = ref('viewer')
</script>

<template>
    <!-- Entrada de texto -->
    <input v-model="title" type="text" />
    <p>Escribiendo: {{ title }}</p>

    <!-- Checkbox -->
    <input v-model="agreed" type="checkbox" />
    <p>Aceptado: {{ agreed }}</p>

    <!-- Select -->
    <select v-model="role">
        <option value="viewer">Lector</option>
        <option value="editor">Editor</option>
        <option value="admin">Administrador</option>
    </select>
</template>

: (v-bind) y @ (v-on)

  • :attr="value": enlaza un valor dinámico a un atributo HTML (abreviatura de v-bind:attr)
  • @event="handler": registra un event listener (abreviatura de v-on:event)
<template>
    <!-- Binding dinámico de atributos -->
    <img :src="imageUrl" :alt="imageAlt" />

    <!-- Handlers de eventos -->
    <button @click="handleClick">Clic</button>
    <form @submit.prevent="handleSubmit">...</form>
</template>

Fundamentos del componente de página

Los componentes de página de Inertia son componentes Vue normales. Los datos pasados desde el controlador de Laravel se reciben como props.

Controlador

// app/Http/Controllers/PostController.php
use Inertia\Inertia;
use App\Models\Post;

class PostController extends Controller
{
    public function index()
    {
        return Inertia::render('Posts/Index', [
            'posts' => Post::latest()->paginate(10),
        ]);
    }
}

Componente de página Vue

<!-- resources/js/pages/Posts/Index.vue -->
<script setup>
import { Link } from '@inertiajs/vue3'

defineProps({
    posts: Object,
})
</script>

<template>
    <div>
        <h1>Lista de publicaciones</h1>
        <article v-for="post in posts.data" :key="post.id">
            <h2>
                <Link :href="`/posts/${post.id}`">{{ post.title }}</Link>
            </h2>
            <p>{{ post.created_at }}</p>
        </article>
    </div>
</template>
Solo con declarar las props con defineProps() puedes usar en la plantilla los datos pasados desde el controlador. No hace falta definir una API REST.
Al usar el componente <Link> que ofrece @inertiajs/vue3, la navegación entre páginas se hace por XHR y se evita la recarga completa del navegador.
<script setup>
import { Link } from '@inertiajs/vue3'
</script>

<template>
    <!-- Enlace básico -->
    <Link href="/posts">Lista de publicaciones</Link>

    <!-- Enlace con método POST (para borrar, etc.) -->
    <Link href="/posts/1" method="delete" as="button" type="button">
        Borrar
    </Link>

    <!-- Preload (precarga al pasar el ratón) -->
    <Link href="/posts/1" preload>Ver publicación</Link>
</template>
Se escribe igual que una etiqueta <a> normal, pero por debajo Inertia solo cambia el componente de página, con lo que se logra una sensación de operación como en una SPA.

Componente Form

El componente <Form> que ofrece @inertiajs/vue3 es el estilo recomendado de envío de formularios que se usa en las pantallas de autenticación del starter kit. Se indican action y method como props, y se accede a errors y processing mediante v-slot.

Uso básico

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

<template>
    <Form action="/posts" method="post" class="flex flex-col gap-4" v-slot="{ errors, processing }">
        <div>
            <label for="title">Título</label>
            <input id="title" name="title" type="text" required />
            <p v-if="errors.title" class="error">{{ errors.title }}</p>
        </div>

        <div>
            <label for="content">Contenido</label>
            <textarea id="content" name="content"></textarea>
            <p v-if="errors.content" class="error">{{ errors.content }}</p>
        </div>

        <button type="submit" :disabled="processing">
            {{ processing ? 'Enviando...' : 'Publicar' }}
        </button>
    </Form>
</template>
v-slot="{ errors, processing }" es la sintaxis de slot con scope de Vue; el componente Form calcula automáticamente estos valores y te los pasa. En los campos del formulario, en lugar de v-model, se usa el atributo nativo name de HTML, y funciona la recolección estándar de datos de formulario del navegador.

Patrón del starter kit

El starter kit gestiona las rutas como objetos usando Wayfinder. store.form() devuelve un objeto que incluye action y method del objeto de ruta, y se aplica al <Form> con v-bind.
<script setup lang="ts">
import { Form } from '@inertiajs/vue3'
import { store } from '@/routes/login'
</script>

<template>
    <Form
        v-bind="store.form()"
        :reset-on-success="['password']"
        v-slot="{ errors, processing }"
        class="flex flex-col gap-6"
    >
        <!-- Contenido del formulario -->
    </Form>
</template>
Los campos indicados en reset-on-success se resetean automáticamente al enviar con éxito. Se indican en campos que se quieren dejar vacíos tras el envío, como la contraseña.
Si no usas Wayfinder, pasando directamente la URL con action="/login" funciona igual.

Helper useForm

Para el procesamiento de formularios se usa el helper useForm de @inertiajs/vue3. La gestión del estado del formulario, el envío y la visualización de errores de validación se implementan de forma sencilla.

Lado del controlador

// app/Http/Controllers/PostController.php
class PostController extends Controller
{
    public function store(Request $request)
    {
        $validated = $request->validate([
            'title'   => ['required', 'string', 'max:255'],
            'content' => ['required', 'string'],
        ]);

        Post::create($validated + ['user_id' => auth()->id()]);

        return redirect()->route('posts.index')
            ->with('success', 'Se ha creado la publicación.');
    }
}

Componente de formulario Vue

<!-- resources/js/pages/Posts/Create.vue -->
<script setup>
import { useForm } from '@inertiajs/vue3'

const form = useForm({
    title: '',
    content: '',
})

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

<template>
    <form @submit.prevent="submit">
        <div>
            <label>Título</label>
            <input v-model="form.title" type="text" />
            <p v-if="form.errors.title" class="error">{{ form.errors.title }}</p>
        </div>

        <div>
            <label>Contenido</label>
            <textarea v-model="form.content"></textarea>
            <p v-if="form.errors.content" class="error">{{ form.errors.content }}</p>
        </div>

        <button type="submit" :disabled="form.processing">
            {{ form.processing ? 'Enviando...' : 'Publicar' }}
        </button>
    </form>
</template>
Resumen de las principales propiedades del objeto que devuelve useForm.
Propiedad / MétodoDescripción
form.dataObjeto de datos del formulario
form.errorsErrores de validación (acceso por nombre de campo)
form.processingtrue durante el envío (útil para desactivar el botón)
form.isDirtytrue si se ha cambiado respecto al valor inicial
form.post(url)Enviar con petición POST
form.put(url)Enviar con petición PUT (actualización)
form.delete(url)Enviar con petición DELETE
form.reset()Resetear el formulario a los valores iniciales
Cuando se devuelve un error de validación, useForm mantiene el contenido introducido y muestra el error. Combinado con v-model se logra una experiencia de formulario sin fisuras.

Datos compartidos (Shared Data)

Los datos comunes a todas las páginas (información del usuario autenticado, mensajes flash, etc.) se definen en el método share() del middleware HandleInertiaRequests.
// app/Http/Middleware/HandleInertiaRequests.php
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()
                    ? $request->user()->only('id', 'name', 'email')
                    : null,
            ],
            'flash' => [
                'success' => $request->session()->get('success'),
                'error'   => $request->session()->get('error'),
            ],
        ]);
    }
}
Para acceder a los datos compartidos desde un componente Vue se usa usePage().
<script setup>
import { computed } from 'vue'
import { usePage } from '@inertiajs/vue3'

const page = usePage()

// Acceso a los datos compartidos
const user = computed(() => page.props.auth.user)
const flash = computed(() => page.props.flash)
</script>

<template>
    <header>
        <span v-if="user">{{ user.name }}</span>
        <span v-else>Invitado</span>
    </header>

    <div v-if="flash.success" class="alert-success">
        {{ flash.success }}
    </div>
</template>
Como los datos compartidos se incluyen en todas las peticiones, se recomienda limitarlos al mínimo necesario. Si los pasas mediante una evaluación diferida con fn(), solo se evalúan cuando realmente se acceden.

Fundamentos de reactividad de Vue 3

Presentamos las APIs de reactividad de Vue 3 que conviene conocer para desarrollar con Inertia × Vue.

ref — Valor reactivo primitivo

<script setup>
import { ref } from 'vue'

const count = ref(0)
const isOpen = ref(false)

// Acceso con .value (en la plantilla no es necesario)
count.value++
</script>

<template>
    <p>{{ count }}</p>
    <button @click="isOpen = !isOpen">Toggle</button>
</template>

computed — Propiedad calculada

<script setup>
import { ref, computed } from 'vue'

const posts = ref([])

const publishedPosts = computed(() =>
    posts.value.filter(post => post.published)
)
</script>

onMounted — Procesamiento tras el mount

<script setup>
import { onMounted } from 'vue'

onMounted(() => {
    console.log('El componente se ha montado')
})
</script>

Conclusión

Vue.js tiene una gran afinidad con Laravel y saca su potencial, especialmente en la configuración de “monolito moderno” a través de Inertia.
ElementoRol
Controlador de LaravelEnrutamiento, obtención de datos y validación
Inertia::render()Pasa datos del controlador al componente Vue
Componente de página VueRecibe props y renderiza la UI
useFormGestión del estado del formulario, envío y visualización de errores
Componente LinkNavegación entre páginas sin recarga completa
usePage().propsAcceso a los datos compartidos
Con Inertia × Vue puedes disfrutar tanto de la sencillez del backend de Laravel como de la UI reactiva de Vue. Si creas el proyecto con el starter kit, puedes empezar a desarrollar de inmediato con las pantallas de autenticación incluidas.

Documentación oficial de Inertia.js

Para todas las funcionalidades de Inertia v3, consulta la documentación oficial.
Última modificación el 13 de julio de 2026