> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Introducción a Vue.js — Fundamentos para usarlo con Inertia × Laravel

> Presentamos Vue.js, el framework JS más familiar para los usuarios de Laravel. Se explica de forma práctica desde una visión de Options API / Composition API hasta los componentes de página, useForm y los datos compartidos con Inertia v3 × Vue 3.

## ¿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".

<Info>
  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.
</Info>

### 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.

```vue theme={null}
<!-- 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.

```vue theme={null}
<!-- 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>
```

<Tip>
  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>`.
</Tip>

***

## 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`.

```mermaid theme={null}
timeline
    title Recorrido de Laravel y Vue
    2016 : Laravel 5.3 — Vue adoptado por defecto
    2019 : Laravel 6 — El scaffold de Vue se separa al paquete laravel/ui
    2021 : Laravel 8 — Aparece el starter kit Jetstream + Inertia (Vue)
    2022 : Laravel 9 — Migración a Vite
    2025 : Laravel 12 — Renovación de los starter kits (Vue / React)
    2026 : Laravel 13 — Starter kits compatibles con Inertia v3
```

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.

```mermaid theme={null}
graph LR
    Browser["Navegador"]
    Inertia["Inertia.js<br>(capa de adaptador)"]
    Laravel["Laravel<br>(controlador)"]
    Vue["Vue<br>(componente de página)"]

    Browser <-->|XHR / carga de página completa| Inertia
    Inertia <-->|respuesta Inertia| Laravel
    Inertia -->|props| Vue
    Vue -->|renderiza| Browser
```

***

## Configuración

### Vía starter kit (recomendado)

Al iniciar un proyecto nuevo, usar el starter kit es lo más sencillo.

```shell theme={null}
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.

```shell theme={null}
# 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`.

```js theme={null}
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`.

```js theme={null}
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)
    },
})
```

<Info>
  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](https://inertiajs.com/installation).
</Info>

***

## 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.

```vue theme={null}
<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

```vue theme={null}
<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

```vue theme={null}
<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.

```vue theme={null}
<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`)

```vue theme={null}
<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

```php theme={null}
// 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

```vue theme={null}
<!-- 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.

***

## Componente `Link`

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.

```vue theme={null}
<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

```vue theme={null}
<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](/es/blog/wayfinder-introduction). `store.form()` devuelve un objeto que incluye `action` y `method` del objeto de ruta, y se aplica al `<Form>` con `v-bind`.

```vue theme={null}
<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.

<Info>
  Si no usas Wayfinder, pasando directamente la URL con `action="/login"` funciona igual.
</Info>

***

## 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

```php theme={null}
// 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

```vue theme={null}
<!-- 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étodo | Descripción                                             |
| ------------------ | ------------------------------------------------------- |
| `form.data`        | Objeto de datos del formulario                          |
| `form.errors`      | Errores de validación (acceso por nombre de campo)      |
| `form.processing`  | `true` durante el envío (útil para desactivar el botón) |
| `form.isDirty`     | `true` 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`.

```php theme={null}
// 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()`.

```vue theme={null}
<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>
```

<Info>
  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.
</Info>

***

## 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

```vue theme={null}
<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

```vue theme={null}
<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

```vue theme={null}
<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.

| Elemento                 | Rol                                                                 |
| ------------------------ | ------------------------------------------------------------------- |
| Controlador de Laravel   | Enrutamiento, obtención de datos y validación                       |
| `Inertia::render()`      | Pasa datos del controlador al componente Vue                        |
| Componente de página Vue | Recibe props y renderiza la UI                                      |
| `useForm`                | Gestión del estado del formulario, envío y visualización de errores |
| Componente `Link`        | Navegación entre páginas sin recarga completa                       |
| `usePage().props`        | Acceso 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.

<Card title="Documentación oficial de Inertia.js" icon="book-open" href="https://inertiajs.com">
  Para todas las funcionalidades de Inertia v3, consulta la documentación oficial.
</Card>


## Related topics

- [Introducción a React — Fundamentos para usarlo con Inertia × Laravel](/es/blog/react-introduction.md)
- [Introducción a Svelte — Fundamentos para usarlo con Inertia × Laravel](/es/blog/svelte-introduction.md)
- [Construir una SPA con Inertia.js](/es/blog/inertia-introduction.md)
- [Frontend](/es/frontend.md)
- [Introducción a Laravel Nightwatch](/es/blog/nightwatch-introduction.md)
