vitejs/vite · error · Error

Cannot deep clone non-plain object

Error message

Cannot deep clone non-plain object

What it means

Vite's deepClone utility recursively clones arrays, plain objects (via isObject), functions (passed by reference), and RegExps. Any other non-null object that is not a plain object or array — e.g. a Map, Set, Date, class instance, Error, URL, or Buffer — falls through to the final branch and throws, because Vite cannot safely deep-clone arbitrary structured objects without risking data loss or prototype issues.

Source

Thrown at packages/vite/src/node/utils.ts:1231

export function deepClone<T>(value: T): DeepWritable<T> {
  if (Array.isArray(value)) {
    return value.map((v) => deepClone(v)) as DeepWritable<T>
  }
  if (isObject(value)) {
    const cloned: Record<string, any> = {}
    for (const key in value) {
      cloned[key] = deepClone(value[key])
    }
    return cloned as DeepWritable<T>
  }
  if (typeof value === 'function') {
    return value as DeepWritable<T>
  }
  if (value instanceof RegExp) {
    return new RegExp(value) as DeepWritable<T>
  }
  if (typeof value === 'object' && value != null) {
    throw new Error('Cannot deep clone non-plain object')
  }
  return value as DeepWritable<T>
}

type MaybeFallback<D, V> = undefined extends V ? Exclude<V, undefined> | D : V

type MergeWithDefaultsResult<D, V> =
  Equal<D, undefined> extends true
    ? V
    : D extends Function | Array<any>
      ? MaybeFallback<D, V>
      : V extends Function | Array<any>
        ? MaybeFallback<D, V>
        : D extends Record<string, any>
          ? V extends Record<string, any>
            ? {
                [K in keyof D | keyof V]: K extends keyof D
                  ? K extends keyof V

View on GitHub (pinned to 89620f09af)

Solutions

  1. Replace the non-plain object with a plain object literal (convert Map/Set to arrays/objects, Date to ISO string, class instance to a plain serializable shape).
  2. If the value is meant to be shared by reference, pass a function instead — deepClone passes functions through without cloning.
  3. Restructure so the non-plain object is constructed lazily inside a plugin hook rather than stored in cloned config.

Example fix

// before — Date instance fails deepClone
const config = {
  myPlugin: { startedAt: new Date() },
}

// after — store as ISO string, reconstruct when needed
const config = {
  myPlugin: { startedAt: new Date().toISOString() },
}
Defensive patterns

Strategy: type-guard

Validate before calling

// Validate config values are plain-serializable before passing to Vite
function isPlainlyCloneable(v: unknown): boolean {
  if (v === null || typeof v !== 'object') return true
  if (v instanceof RegExp || typeof v === 'function') return true
  if (Array.isArray(v)) return v.every(isPlainlyCloneable)
  const proto = Object.getPrototypeOf(v)
  if (proto !== Object.prototype && proto !== null) return false
  return Object.values(v).every(isPlainlyCloneable)
}

if (!isPlainlyCloneable(myConfigValue)) {
  throw new Error('Config contains a non-plain object that Vite cannot deep-clone')
}

Type guard

function isPlainObject(v: unknown): v is Record<string, unknown> {
  if (v === null || typeof v !== 'object') return false
  const proto = Object.getPrototypeOf(v)
  return proto === Object.prototype || proto === null
}

Prevention

When it happens

Trigger: Passing a config value (or any object processed by deepClone) that is a class instance, Map, Set, Date, Error, URL, or other built-in non-plain object. deepClone is used internally when normalizing/merging certain config structures, so a custom object placed where Vite expects a plain record triggers it.

Common situations: Putting a class instance, Date, Map, or Set into Vite config (e.g. a plugin option, define value, or resolved config field) that gets deep-cloned. Passing a third-party library object (like a compiled schema or AST node instance) where a plain config object is expected. Custom resolvers that attach non-plain metadata.

Related errors


AI-assisted analysis of vitejs/vite@89620f09af (2026-08-03). Data as JSON: /data/errors/1473b1ba0c630b81.json. Report an issue: GitHub.