InertiaLink y Link en Laravel Inertia

Video thumbnail

Para crear enlaces dentro de una aplicación de Inertia lo cual es ideal para nuestros formularios con Drag and Drop, deberá usar el componente de enlace de Inertia. Este es un envoltorio ligero alrededor de un enlace de anclaje estándar <a> que intercepta eventos de clic y evita que se produzcan recargas de página completa. Así es como Inertia proporciona una experiencia de aplicación de una sola página.

Vamos a hablar sobre un cambio importante que tenemos en las últimas actualizaciones de de Inertia en Laravel, y es un cambio de organización interna de Inertia; específicamente el cambio de nombre de un módulo de Vue:

https://inertiajs.com/releases/inertia-vue3-0.5.0-2021-07-13

Ahora nuestros InertiaLink se llaman como Link:

<inertialink method="DELETE" :href="route('user.destroy', { customer: u })">Borrar</Link>

Aparte de esto, tenemos que registrarlo para poder emplearlo

import { Link } from '@inertiajs/vue3' // vue 3

o

import { Link } from '@inertiajs/vue' // vue 2
components: {
    ...
    Link
},

Y lo podemos emplear sin problemas de la misma manera; en este aspecto, no tenemos ningún cambio:

<Link  method="DELETE" :href="route('user.destroy', { customer: u })">Borrar</Link>

Métodos

Puede especificar el método para una solicitud de vínculo de inercia. El valor predeterminado es GET, pero también puede usar POST, PUT, PATCH y DELETE.

import { Link } from '@inertiajs/vue3'
<Link href="/logout" method="post">Logout</Link>

Header

Al igual que incluir headers:

import { Link } from '@inertiajs/vue3'
<Link href="/endpoint" :headers="{ foo: bar }">Save</Link>

Remplazar la pila al navegar

Puede especificar el comportamiento del historial del navegador. Por defecto, las visitas a la página insertan el estado (nuevo) (window.history.pushState) en el historial; sin embargo, también es posible reemplazar el estado (window.history.replaceState) configurando el atributo replace en verdadero. Esto hará que la visita reemplace el estado de historial actual, en lugar de agregar un nuevo estado de historial a la pila:

import { Link } from '@inertiajs/vue3'
<Link href="/" replace>Home</Link>

Objeto router y preserveScroll

Video thumbnail

Cuando trabajamos con formularios u operaciones CRUD en Laravel + Inertia, es común encontrarnos con problemas de sincronización, especialmente cuando queremos actualizar la interfaz sin tener que recurrir a un window.location.reload(), que aunque “funciona”, es una mala práctica y además causa inconsistencias.

Ejemplo NO recomendado (aunque útil como último recurso):

create() {
   this.todoSelected = 0
   router.post(route('todo.store'), { name: this.form.name })

   setTimeout(() => window.location.reload(), 500) // ❌ Horrible pero funcional
}

El problema es que ese timeOut no está sincronizado con la operación real, así que puede recargar demasiado rápido o demasiado tarde.

✅ Solución real: usar los Callbacks del router

Inertia provee callbacks que se ejecutan dependiendo del estado de la petición:

Callback    Cuándo se ejecuta
onBefore    Antes de iniciar la visita
onStart    Cuando Inertia empieza a procesar
onProgress    Durante la subida de archivos
onSuccess    Cuando la petición fue exitosa
onError    Cuando hay errores de validación o server
onFinish    Siempre que finaliza (éxito o error)

Sintaxis general:

router.<method>(url, data, {
   onBefore: visit => {},
   onStart: visit => {},
   onProgress: progress => {},
   onSuccess: page => {},
   onError: errors => {},
   onFinish: visit => {},
})

✅ Ejemplo real: eliminar un TODO y actualizar la lista sin recargar

Tú implementaste correctamente el callback onSuccess.

La lógica queda así:

router.delete(route('todo.destroy', this.deleteTodoRow), {
   onSuccess: (page) => {
       console.log(page.props.todos)
       this.dtodos = page.props.todos
   },
})

¿Qué es page?

Es un objeto que contiene:

  • component
  • props
  • url
  • version

Dentro de page.props vienen los mismos datos que envías desde el servidor.

En tu caso:

page.props.todos

contiene la lista de TODOS actualizada, que puedes reasignar directamente al estado del componente (this.dtodos).

Esto elimina la necesidad de recargar manualmente la página.

✅ Preservar el scroll

Otro problema típico: el scroll "salta" al inicio al actualizar la página virtual de Inertia.

Solución:

router.delete(route('todo.destroy', this.deleteTodoRow), {
   preserveScroll: true,
   onSuccess: (page) => {
       this.dtodos = page.props.todos
   },
})

Con preserveScroll: true, la interfaz mantiene su posición exacta en pantalla.

Notas importantes sobre tu implementación

✔ Lo más complicado

Lo más difícil suele ser encajar correctamente:

  • la ruta
  • los parámetros
  • la data

El objeto de opciones con callbacks

Ejemplo correctamente estructurado:

router.put(
   route('todo.update', this.todo.id),
   { name: this.form.name },     // ← data
   {
       preserveScroll: true,     // ← opciones
       onSuccess: (page) => {
           this.dtodos = page.props.todos
       }
   }
)

⭐ Conclusión y recomendación final

Para evitar recargar la página y resolver problemas de sincronización, usa siempre los callbacks del router, especialmente:

  • onSuccess → cuando quieres aplicar cambios basados en la respuesta
  • onFinish → si quieres ejecutar algo siempre
  • preserveScroll → para mantener posición del usuario

Ejemplo final limpio:

router.delete(route('todo.destroy', this.deleteTodoRow), {
   preserveScroll: true,
   onSuccess: (page) => {
       this.dtodos = page.props.todos
   },
})

Con esto:

  • no hay necesidad de setTimeout
  • no hay recarga manual
  • no se pierde el scroll
  • tus datos quedan sincronizados correctamente

Opción de Eliminar en CRUD

Video thumbnail

Otro buen uso de los Link en Inertia, es el uso para eliminar registros, veamos una implementación.

Vamos a implementar la funcionalidad para eliminar una categoría; colocamos un nuevo enlace usando el componente de Link e indicamos el método de tipo DELETE en el TD de las acciones en el listado.

Como los enlaces (<a>) siempre hacen peticiones GET, debemos indicar explícitamente que queremos enviar una solicitud DELETE.
Esto se hace con la propiedad method="delete":

resources/js/Pages/Dashboard/Category/Index.vue

***
<td class="p-2">
    <Link :href="route('category.edit', c.id)">Edit</Link>
    <Link method="DELETE" :href="route('category.destroy', c.id)">Delete</Link>
</td>
***
  • method="delete" → Inertia enviará una petición DELETE.
  • as="button" → evita la advertencia en consola y convierte el <Link> en un botón.
  • type="button" → opcional, pero recomendado.

Y desde el controlador, es exactamente el mismo código usado en Laravel básico, que es llamar a la función de delete() de Eloquent:

app/Http/Controllers/Dashboard/CategoryController.php

public function destroy(Category $category)
{
    $category->delete();
}

Debemos registrar la ruta destroy, que recibirá el ID de la categoría a eliminar:

Route::delete('/category/{category}', [CategoryController::class, 'destroy'])
   ->name('category.destroy');

Si vemos en la consola del navegador desde el componente de Index.vue de las categorías, veremos una advertencia como la siguiente:

...links is discouraged as it causes "Open Link in New Tab/Window" accessibility issues. Instead, consider using a more appropriate element, such as a <button>.

Tal cual indica, renderizar un enlace para este tipo de operaciones no es lo recomendado ya que, el usuario puede abrir una pestaña en el navegador; para evitar este comportamiento, hay que convertir el enlace a un botón; para ello:

<Link as="button" type="button" method="DELETE" class="text-sm text-red-400 hover:text-red-700 ml-2" :href="route('category.destroy', c.id)">Delete</Link>

Resolver links is discouraged as it causes "Open Link in New Tab/Window"

Video thumbnail

Si revisaste la consola del navegador cuando eliminaste un registro, seguramente viste una advertencia similar a esta:

Using <Link> for POST, PUT, PATCH or DELETE requests is discouraged.”

Esto ocurre porque el componente <Link> de Inertia está pensado para navegación, no para operaciones mutadoras (crear, actualizar, eliminar).

✔ Solución recomendada por Inertia

Convertir el <Link> en un elemento button utilizando la propiedad as, así:

<Link
   method="delete"
   :href="route('todo.destroy', todo.id)"
   as="button"
>
   Eliminar
</Link>

También puedes agregar type="button" para mayor claridad, aunque no es estrictamente necesario:

<Link
   method="delete"
   :href="route('todo.destroy', todo.id)"
   as="button"
   type="button"
>
   Eliminar
</Link>

Con este cambio:

Ya no aparece la advertencia en consola.

Sigues usando la funcionalidad del <Link> de Inertia.

El elemento se comporta como un botón nativo, que es la forma correcta para operaciones no navegacionales.

Conclusiones

Así que es eso, ahora tenemos un nuevo esquema para trabajar con los enlaces cuyo cambios es SOLO un nombre, para referir nuestras rutas que ahora es mas corto; por lo demás, puedes hacer exactamente lo mismo.

El siguiente paso, aprende a usar los diálogos de confirmación en Laravel Inertia.

Acepto recibir anuncios de interes sobre este Blog.

Vemos una actualización de como migrar de InertiaLink al nuevo enfoque que implemento la gente de Inertia Laravel.

| 👤 Andrés Cruz

🇺🇸 In english