Thanks to visit codestin.com
Credit goes to github.com

Skip to content

schema-org: merge() crashes on non-array target in potentialAction during SPA navigation #709

Description

@fskarmeta

Environment

  • @unhead/schema-org: 2.1.12 (via nuxt-schema-org 6.0.4 / @nuxtjs/seo 5.0.2)
  • unhead: 2.1.12
  • Node.js: v22
  • Nuxt: 4.x
  • Vue: 3.5.30

Reproduction

No repo link — the issue is visible directly in the source code and reproducible with any Nuxt app using defineWebSite with a SearchAction whose target is an object.

  1. In app.vue, define a WebSite with a SearchAction using an object target (which the SearchAction type explicitly allows):
useSchemaOrg([
  defineWebSite({
    name: 'My Site',
    url: siteUrl,
    potentialAction: {
      '@type': 'SearchAction',
      'target': {
        '@type': 'EntryPoint',
        'urlTemplate': `${siteUrl}/search?q={search_term_string}`,
      },
      'query-input': {
        '@type': 'PropertyValueSpecification',
        'valueRequired': true,
        'valueName': 'search_term_string',
      },
    },
  }),
])
  1. Perform SPA navigation (e.g., click a <NuxtLink> to another page and back).
  2. Observe the error in the browser console.

Describe the bug

During SPA navigation, the merge() function in @unhead/schema-org crashes with TypeError: byType[type].target is not iterable when merging duplicate potentialAction entries whose target is a plain object or string (not an array).

Root cause: In packages/schema-org/src/core/util.ts lines 38-39, the merge function spreads target assuming it's always an array:

if (action.target && byType[type].target)
  byType[type].target = [...new Set([...byType[type].target, ...action.target])]

However, the SearchAction type definition (SearchAction/index.ts line 25-28) allows target to be a string or an EntryPoint object:

'target': SearchTarget | {
  '@type'?: 'EntryPoint'
  'urlTemplate'?: SearchTarget
}

And the resolver's own defaults (line 42-44) set target to an object:

defaults: {
  '@type': 'SearchAction',
  'target': { '@type': 'EntryPoint' },
}

So the types and the resolver both allow/produce non-array target, but the merge function crashes on it.

Suggested fix — normalize target to an array before spreading:

if (action.target && byType[type].target) {
  const a = Array.isArray(byType[type].target) ? byType[type].target : [byType[type].target]
  const b = Array.isArray(action.target) ? action.target : [action.target]
  byType[type].target = [...new Set([...a, ...b])]
}

Workaround — wrap target in an array in user code:

'target': [{
  '@type': 'EntryPoint',
  'urlTemplate': `${siteUrl}/search?q={search_term_string}`,
}],

I'm happy to submit a PR with the fix + a regression test if this approach looks right.

Additional context

The error only occurs during SPA navigation (not on full page load or SSR), because that's when the same WebSite schema definition gets merged with itself as routes change. It does not appear in production when pages are server-rendered on each navigation.

Logs

Uncaught (in promise) TypeError: byType[type].target is not iterable
    at merge (schema-org.BcpQpVeE.mjs:307:84)
    at Object.resolveGraph (schema-org.BcpQpVeE.mjs:378:37)
    at tags:resolve (schema-org.BcpQpVeE.mjs:677:43)
    at async Object.resolveTags (unhead.Ct24BOby.mjs:141:7)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions