-
Notifications
You must be signed in to change notification settings - Fork 515
Description
@inertiajs/vue3 Version
2.0.17
Backend stack (optional)
- Inertia.js Vue3 Adapter:
^2.0.17
- Inertia Laravel:
^2.0.4
- Vue:
^3.5.18
- Laravel:
^12.0
- PHP:
^8.3
Related Issues
This appears to be a regression of issue #775, which was resolved in v1.x but has returned in v2.0 due to the new client-side routing architecture not properly handling Vue
Describe the problem
After upgrading to Inertia.js v2.0, visiting pages that contain Vue reactive data results in a DataCloneError
when the History API is called. This appears to be related to v2.0's new client-side routing mechanism directly passing Vue reactive Proxy objects to History.replaceState()
.
Error
Uncaught (in promise) DataCloneError: Failed to execute 'replaceState' on 'History': #<Object> could not be cloned. at History.doReplaceState (history.ts:185:20) at doReplace (history.ts:164:16) at history.ts:171:11
Possible cause
Vue 3's reactive system wraps objects in Proxy instances that cannot be serialized by the browser's structuredClone
algorithm used by the History API. In v2.0, these reactive objects are being passed directly to History.replaceState()
without being cleaned of their reactivity.
Minimal Reproduction
Laravel Controller:
<?php
class ProductController extends Controller
{
public function index(Request $request)
{
return Inertia::render('Products/Index', [
'data' => [
'items' => Product::paginate(10),
'filters' => [...], // Any nested data structure
]
]);
}
}
Vue component
<template>
<div>
<h1>Products</h1>
<!-- Any component that uses the reactive data -->
</div>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
data: Object
})
// Any reactive computation using the props
const items = computed(() => props.data.items)
</script>
Expected behaviour
Pages should load without DataCloneError, as they did in Inertia.js v1.x.
Steps to reproduce
- Create a page with reactive data from Laravel controller
- Visit the page directly (e.g., /products)
- Error occurs on initial page load
Adopted temporal workaround
// app.js - before Vue imports
import { toRaw } from 'vue'
const originalReplaceState = History.prototype.replaceState;
const originalPushState = History.prototype.pushState;
function deepCleanReactivity(obj) {
if (obj === null || obj === undefined || typeof obj !== 'object') {
return obj;
}
const rawObj = toRaw(obj);
if (Array.isArray(rawObj)) {
return rawObj.map(deepCleanReactivity);
}
const cleaned = {};
for (const [key, value] of Object.entries(rawObj)) {
cleaned[key] = deepCleanReactivity(value);
}
return cleaned;
}
History.prototype.replaceState = function(state, title, url) {
const cleanedState = deepCleanReactivity(state);
return originalReplaceState.call(this, cleanedState, title, url);
};
History.prototype.pushState = function(state, title, url) {
const cleanedState = deepCleanReactivity(state);
return originalPushState.call(this, cleanedState, title, url);
};