Skip to main content

VelaFront / headless

The SEO Suite is headless-first. Every storefront feature has a GraphQL counterpart, exposed on the standard Magento types so VelaFront, PWA Studio, Hyvä Checkout, or any custom Next.js storefront can consume the same data the rendered Luma page sees.

The seo: SeoMetadata field

Available on:

  • ProductInterface
  • CategoryInterface
  • CmsPage
  • CmsPageItem

Returns:

type SeoMetadata {
title: String
description: String
robots: String
canonical: String
open_graph: [SeoTag!]!
structured_data: [String!]! # JSON-LD docs as raw JSON strings
}

type SeoTag {
name: String! # e.g. "og:title", "twitter:card"
content: String!
}

Full schema reference: GraphQL → SEO Metadata.

Example product query

query Product($urlKey: String!) {
products(filter: { url_key: { eq: $urlKey } }) {
items {
sku
name
seo {
title
description
robots
canonical
open_graph { name content }
structured_data
}
}
}
}

Rendering in Next.js (App Router)

// app/products/[urlKey]/page.tsx
import type { Metadata } from 'next';

export async function generateMetadata({ params }): Promise<Metadata> {
const { products } = await graphqlFetch(PRODUCT_QUERY, { urlKey: params.urlKey });
const seo = products.items[0]?.seo;
if (!seo) return {};

const ogTags = Object.fromEntries(seo.open_graph.map(t => [t.name, t.content]));

return {
title: seo.title,
description: seo.description,
robots: seo.robots,
alternates: { canonical: seo.canonical },
openGraph: {
title: ogTags['og:title'],
description: ogTags['og:description'],
url: ogTags['og:url'],
images: ogTags['og:image'] ? [{ url: ogTags['og:image'] }] : [],
type: ogTags['og:type'] as 'website' | 'product',
siteName: ogTags['og:site_name'],
},
twitter: {
card: ogTags['twitter:card'] as 'summary' | 'summary_large_image',
title: ogTags['twitter:title'],
description: ogTags['twitter:description'],
images: ogTags['twitter:image'] ? [ogTags['twitter:image']] : [],
site: ogTags['twitter:site'],
},
};
}

export default function ProductPage({ /* ... */ }) {
const seo = /* same query as above, or pass through from parent */;

return (
<>
{seo.structured_data.map((json, i) => (
<script
key={i}
type="application/ld+json"
// SEO Suite already escapes </ — JSON output is safe to inject as-is
dangerouslySetInnerHTML={{ __html: json }}
/>
))}
{/* …product UI… */}
</>
);
}

What about the storefront templates?

When you run headless, Magento's Luma/Hyvä storefront isn't rendered — so the four head.additional templates are irrelevant. The GraphQL field is the canonical surface.

Caching

The GraphQL response participates in Magento's standard GraphQL cache (Varnish / FPC tag-based invalidation). Tags fired include:

  • cat_p_<product_id> — product changes invalidate
  • cat_c_<category_id> — category changes invalidate
  • cms_p_<page_id> — CMS-page changes invalidate
  • cfg — config changes (e.g. flipping JSON-LD off) invalidate

So the response is cached aggressively but invalidated cleanly when admin edits anything material.

Existing v1 GraphQL fields preserved

meta_robots: String is still available on CategoryInterface, CmsPage, and CmsPageItem for backward compatibility — the new seo field is additive.

Limitations

  • Hreflang links are not yet exposed via GraphQL — the storefront emits them, but the headless surface does not (yet). Roadmapped for v2.9.
  • URL-relationship overrides (manual canonical/alternate mappings in byte8_seosuite_url) are surfaced through canonical for CMS pages but not for products/categories where Magento's built-in helper drives the value.

Next