What Causes This Error?
This error occurs when you register a payload reviver too late in the application lifecycle. Payload revivers need to be registered before the payload is processed, so they must be in a plugin that runs early.
Understanding Payload Revivers
Payload revivers transform serialized data back into JavaScript objects during hydration. They’re used for custom types like Date objects, RegExp, or custom classes.
The Problem
// plugins/late-reviver.ts
export default defineNuxtPlugin(() => {
// ❌ Wrong - might be registered too late
definePayloadReviver('MyClass', (data) => new MyClass(data))
})
The Fix
Use enforce: ‘pre’
// plugins/payload.ts
export default defineNuxtPlugin({
name: 'payload-plugin',
enforce: 'pre', // ✅ Run early
setup() {
definePayloadReviver('MyClass', (data) => new MyClass(data))
}
})
Or Use Order Prefix
// plugins/00.payload.ts ← Runs first (alphabetical order)
export default defineNuxtPlugin(() => {
definePayloadReviver('MyClass', (data) => new MyClass(data))
})
Complete Example
Define Reducer and Reviver
// plugins/00.custom-types.ts
class Point {
constructor(public x: number, public y: number) {}
}
export default defineNuxtPlugin({
name: 'custom-types',
enforce: 'pre',
setup() {
// ✅ Reducer: How to serialize
definePayloadReducer('Point', (value) => {
if (value instanceof Point) {
return { x: value.x, y: value.y }
}
})
// ✅ Reviver: How to deserialize
definePayloadReviver('Point', (data) => {
return new Point(data.x, data.y)
})
}
})
Use in Components
<script setup>
const { data } = await useAsyncData('point', () => {
// Server returns Point instance
return new Point(10, 20)
})
// On client, data.value is properly revived as Point instance
console.log(data.value instanceof Point) // true
</script>
Common Use Cases
Date Objects
// plugins/00.payload-types.ts
export default defineNuxtPlugin({
enforce: 'pre',
setup() {
definePayloadReducer('Date', (value) => {
if (value instanceof Date) {
return value.toISOString()
}
})
definePayloadReviver('Date', (data) => {
return new Date(data)
})
}
})
Custom Classes
// plugins/00.payload-types.ts
import { User } from '~/models/User'
export default defineNuxtPlugin({
enforce: 'pre',
setup() {
definePayloadReducer('User', (value) => {
if (value instanceof User) {
return {
id: value.id,
name: value.name,
email: value.email
}
}
})
definePayloadReviver('User', (data) => {
return new User(data.id, data.name, data.email)
})
}
})
Map and Set
// plugins/00.payload-types.ts
export default defineNuxtPlugin({
enforce: 'pre',
setup() {
// Map
definePayloadReducer('Map', (value) => {
if (value instanceof Map) {
return Array.from(value.entries())
}
})
definePayloadReviver('Map', (data) => {
return new Map(data)
})
// Set
definePayloadReducer('Set', (value) => {
if (value instanceof Set) {
return Array.from(value)
}
})
definePayloadReviver('Set', (data) => {
return new Set(data)
})
}
})
Plugin Ordering
plugins/
├── 00.payload.ts ← Runs first (alphabetically)
├── 01.auth.ts ← Runs second
├── analytics.ts ← Runs later
└── z-last.ts ← Runs last
Or use enforce:
// Pre plugins run first
{ enforce: 'pre' }
// Default plugins run in the middle
{ }
// Post plugins run last
{ enforce: 'post' }
Debugging
// plugins/00.payload-debug.ts
export default defineNuxtPlugin({
enforce: 'pre',
setup() {
console.log('Registering payload handlers...')
definePayloadReducer('Debug', (value) => {
console.log('Reducing:', value)
return value
})
definePayloadReviver('Debug', (data) => {
console.log('Reviving:', data)
return data
})
}
})
Quick Checklist
- Use
enforce: 'pre'on payload plugins - Or prefix filename with
00.for early loading - Register revivers before any data fetching
- Match reducer name with reviver name
- Define both reducer and reviver for custom types
- Plugin must run on both server and client