InertiaLink and Link in Laravel Inertia

To create links within an Inertia application, which is ideal for our drag-and-drop forms, This is a lightweight wrapper around a standard <a> anchor link that intercepts click events and prevents full page reloads from occurring. This is how Inertia provides a single page app experience.

We are going to talk about an important change that we have in the last updates of Inertia in Laravel, and it is a change of internal organization of Inertia; specifically renaming a Vue module:

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>

Apart from this, we have to register to be able to use

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

or

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

And we can use it without problems in the same way; in this aspect, we have no change:

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

Methods

You can specify the method for an inertial link request. The default is GET, but you can also usePOST, PUT, PATCH and DELETE.

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

Header

Just like including headers:

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

Replace battery when browsing

You can specify the behavior of the browser history. By default, page views push the (new) state (window.history.pushState) into the history; however, it is also possible to replace the state (window.history.replaceState) by setting the replace attribute to true. This will cause the visit to replace the current history state, instead of adding a new history state to the stack:

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

Router Object and preserveScroll

When working with forms or CRUD operations in Laravel + Inertia, it's common to encounter synchronization issues, especially when we want to update the interface without resorting to a window.location.reload(), which, although it "works," is a bad practice and also causes inconsistencies.

NOT Recommended Example (though useful as a last resort):

create() {
   this.todoSelected = 0
   router.post(route('todo.store'), { name: this.form.name })
   setTimeout(() => window.location.reload(), 500) 
}

The problem is that this timeOut is not synchronized with the actual operation, so it can reload too fast or too late.

✅ Real Solution: Use the router's Callbacks

Inertia provides callbacks that execute depending on the request status:

  • Callback    When it executes
  • onBefore    Before starting the visit
  • onStart    When Inertia starts processing
  • onProgress    During file upload
  • onSuccess    When the request was successful
  • onError    When there are validation or server errors
  • onFinish    Always when it finishes (success or error)

General Syntax:

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

✅ Real Example: Delete a TODO and update the list without reloading

You correctly implemented the onSuccess callback.

The logic is as follows:

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

What is page?

It is an object that contains:

  • component
  • props
  • url
  • version

Inside page.props come the same data you send from the server.

In your case:

page.props.todos

contains the updated TODOS list, which you can reassign directly to the component state (this.dtodos).

This eliminates the need to manually reload the page.

✅ Preserve Scroll

Another typical problem: the scroll "jumps" to the top when the virtual Inertia page is updated.

Solution:

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

With preserveScroll: true, the interface maintains its exact position on the screen.

🧠 Important Notes on Your Implementation

✔ The trickiest part

The most difficult thing is usually fitting together correctly:

  • the route
  • the parameters
  • the data

The options object with callbacks

Correctly structured example:

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

⭐ Conclusion and Final Recommendation

To avoid reloading the page and solve synchronization problems, always use the router callbacks, especially:

  • onSuccess → when you want to apply changes based on the response
  • onFinish → if you want to execute something always
  • preserveScroll → to maintain the user's position

Clean final example:

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

With this:

  • there is no need for setTimeout
  • there is no manual reload
  • the scroll is not lost
  • your data remains correctly synchronized

Delete Option in CRUD

Another good use of Links in Inertia is for deleting records. Let's look at an implementation.

We're going to implement the functionality to delete a category; we place a new link using the Link component and indicate the DELETE method type in the actions TD in the listing.

Since links (<a>) always make GET requests, we must explicitly indicate that we want to send a DELETE request.
This is done with the property 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 will send a DELETE request.
  • as="button" → avoids the console warning and converts the <Link> into a button.
  • type="button" → optional, but recommended.

And from the controller, it is exactly the same code used in basic Laravel, which is to call Eloquent's delete() function:

app/Http/Controllers/Dashboard/CategoryController.php

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

We must register the destroy route, which will receive the ID of the category to be deleted:

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

If we look in the browser console from the categories Index.vue component, we will see a warning like the following:

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

As indicated, rendering a link for this type of operation is not recommended because the user might open a new browser tab; to avoid this behavior, we need to convert the link to a button; to do this:

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

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

If you checked the browser console when you deleted a record, you likely saw a warning similar to this:

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

This happens because Inertia's <Link> component is designed for navigation, not for mutating operations (create, update, delete).

✔ Inertia's Recommended Solution

Convert the <Link> into a button element using the as property, like this:

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

You can also add type="button" for clarity, although it's not strictly necessary:

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

With this change:

The console warning no longer appears.

You still use the functionality of Inertia's <Link>.

The element behaves like a native button, which is the correct form for non-navigational operations.

Conclusions

So that's it, now we have a new scheme to work with the links whose changes is JUST a name, to refer our routes that is now shorter; otherwise, you can do exactly the same.

The next step is to learn how to use confirmation dialogs in Laravel Inertia.

I agree to receive announcements of interest about this Blog.

We see an update on how to migrate from InertiaLink to the new approach implemented by the people of Inertia Laravel.

| 👤 Andrés Cruz

🇪🇸 En español