Fix: definePayloadReviver Must Be Unshifted in Nuxt

Error message:
definePayloadReviver must be unshifted (registered at the beginning) - payload may not contain the reviver.
ssr 2025-01-25

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