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 3or
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.todoscontains 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.