This bug has been around for a while in Inertia.js (see timeline below), and after digging into it with @jamesst20's help, we figured out the core issue: Inertia is trying to serialize proxy objects when it saves data to the history state. You might run into this using useForm(fixed in 2021), router.remember, or even just scrolling, as Inertia saves scroll positions in history.
To fix it, I’m using JSON.parse(JSON.stringify()). This is great for simple data structures, and it automatically strips out stuff like functions that can cause issues. It’s lighter and more efficient for this specific case than using lodash.cloneDeep(), which keeps things like functions around (and we don’t need that here).
This issue has mostly affected users of the Vue.js adapter since Vue uses proxy objects for its reactivity system. However, it can impact anyone using front-end libraries that rely on proxies, like Svelte 5, which is rolling out its own reactive system based on runes.
UnhandledPromiseRejectionWarning: DataCloneError: Proxy object could not be cloned.
Uncaught (in promise) DOMException: The object could not be cloned. router.ts:397:4
replaceState router.ts:398
saveScrollPositions router.ts:73
resetScrollPositions router.ts:91
g router.ts:381
(Async: promise callback)
g router.ts:379
(Async: promise callback)
setPage router.ts:373
handleInitialPageVisit router.ts:52
init router.ts:41
setup app.js:43
Timeline
I built this timeline to help me keep track of occurrences relative to the previous attempts to fix this issue.
This bug has been around for a while in Inertia.js (see timeline below), and after digging into it with @jamesst20's help, we figured out the core issue: Inertia is trying to serialize proxy objects when it saves data to the history state. You might run into this using
useForm
(fixed in 2021),router.remember
, or even just scrolling, as Inertia saves scroll positions in history.history.replaceState
usesstructuredClone
, which throwsDataCloneError
if it hits anything non-serializable.To fix it, I’m using
JSON.parse(JSON.stringify())
. This is great for simple data structures, and it automatically strips out stuff like functions that can cause issues. It’s lighter and more efficient for this specific case than usinglodash.cloneDeep()
, which keeps things like functions around (and we don’t need that here).This issue has mostly affected users of the Vue.js adapter since Vue uses proxy objects for its reactivity system. However, it can impact anyone using front-end libraries that rely on proxies, like Svelte 5, which is rolling out its own reactive system based on runes.
Reproducible
Stack trace
Timeline
I built this timeline to help me keep track of occurrences relative to the previous attempts to fix this issue.
Fixes #278, #295, #297, #309, #552, #578, #582, #587, #730, #775, #776, #854, #940, #1021, #1227, #1246, #1340