Skip to content

Add SEO metadata support to Weaverse SDK for theme integration #411

@paul-phan

Description

@paul-phan

Overview

The Weaverse SDK (@weaverse/hydrogen and related packages) currently does not provide SEO metadata capabilities for custom pages and other page types. While Shopify Hydrogen themes can leverage resource-based SEO (products, collections, etc.), custom pages created through Weaverse Builder lack metadata integration with theme implementations.

Problem Statement

  1. Missing metadata in HydrogenPageData: The current HydrogenPageData interface lacks SEO fields
  2. WeaverseClient limitations: loadPage() method doesn't fetch or return SEO metadata
  3. No theme integration patterns: Themes lack utilities to consume Weaverse-managed metadata
  4. React 19 compatibility gap: No support for modern React 19 native SEO components
  5. Legacy support needed: Projects not yet on React 19 need React Router meta function helpers

Proposed Solution

Phase 1: Core SDK Updates

HydrogenPageData Interface Extension (@weaverse/hydrogen):

export interface HydrogenPageData extends WeaverseProjectDataType {
  id: string
  name: string
  items: HydrogenComponentData[]
  // NEW: SEO metadata support
  seo?: {
    title?: string
    description?: string
    keywords?: string
    canonicalUrl?: string
    openGraph?: {
      title?: string
      description?: string
      image?: string
      type?: string
    }
    twitter?: {
      cardType?: string
      title?: string
      description?: string
      image?: string
    }
    robots?: {
      index?: boolean
      follow?: boolean
    }
  }
}

WeaverseClient Updates:

class WeaverseClient {
  async loadPage(params: LoadPageParams): Promise<HydrogenPageData> {
    // Fetch page data including SEO metadata from Weaverse API
    const response = await this.fetch(`/api/pages/${params.handle}?includeSeo=true`)
    return {
      ...pageData,
      seo: response.seo // Include SEO data in response
    }
  }
  
  // NEW: Dedicated method for React Router meta functions
  async getPageMeta(params: LoadPageParams): Promise<MetaDescriptor[]> {
    const pageData = await this.loadPage(params)
    return formatMetaDescriptors(pageData.seo)
  }
}

Phase 2: React 19 Native Components

Weaverse SEO Component (@weaverse/react):

interface WeaverseSEOProps {
  pageData: HydrogenPageData
  shopData?: ShopFragment
  fallback?: SeoConfig
}

export function WeaverseSEO({ pageData, shopData, fallback }: WeaverseSEOProps) {
  const seo = pageData.seo || fallback
  
  return (
    <>
      <title>{seo?.title || pageData.name}</title>
      <meta name="description" content={seo?.description} />
      {seo?.keywords && <meta name="keywords" content={seo.keywords} />}
      {seo?.canonicalUrl && <link rel="canonical" href={seo.canonicalUrl} />}
      
      {/* Open Graph */}
      {seo?.openGraph && (
        <>
          <meta property="og:title" content={seo.openGraph.title} />
          <meta property="og:description" content={seo.openGraph.description} />
          <meta property="og:image" content={seo.openGraph.image} />
          <meta property="og:type" content={seo.openGraph.type || 'website'} />
        </>
      )}
      
      {/* Twitter Cards */}
      {seo?.twitter && (
        <>
          <meta name="twitter:card" content={seo.twitter.cardType || 'summary'} />
          <meta name="twitter:title" content={seo.twitter.title} />
          <meta name="twitter:description" content={seo.twitter.description} />
          <meta name="twitter:image" content={seo.twitter.image} />
        </>
      )}
      
      {/* Robots */}
      <meta 
        name="robots" 
        content={`${seo?.robots?.index !== false ? 'index' : 'noindex'},${seo?.robots?.follow !== false ? 'follow' : 'nofollow'}`} 
      />
    </>
  )
}

Phase 3: React Router Meta Function Helpers

Utility Functions (@weaverse/hydrogen):

export function formatMetaFunction(seoData: PageSEOData): MetaDescriptor[] {
  return [
    { title: seoData.title },
    { name: 'description', content: seoData.description },
    { name: 'keywords', content: seoData.keywords },
    { tagName: 'link', rel: 'canonical', href: seoData.canonicalUrl },
    // OpenGraph tags
    { property: 'og:title', content: seoData.openGraph?.title },
    { property: 'og:description', content: seoData.openGraph?.description },
    { property: 'og:image', content: seoData.openGraph?.image },
    { property: 'og:type', content: seoData.openGraph?.type || 'website' },
    // Twitter tags
    { name: 'twitter:card', content: seoData.twitter?.cardType || 'summary' },
    { name: 'twitter:title', content: seoData.twitter?.title },
    { name: 'twitter:description', content: seoData.twitter?.description },
    { name: 'twitter:image', content: seoData.twitter?.image },
    // Robots
    { 
      name: 'robots', 
      content: `${seoData.robots?.index !== false ? 'index' : 'noindex'},${seoData.robots?.follow !== false ? 'follow' : 'nofollow'}` 
    }
  ].filter(tag => tag.content) // Remove undefined values
}

export async function getWeaverseMeta(
  context: AppLoadContext,
  params: LoadPageParams
): Promise<MetaDescriptor[]> {
  const pageData = await context.weaverse.loadPage(params)
  return pageData.seo ? formatMetaFunction(pageData.seo) : []
}

Phase 4: Enhanced Integration Patterns

Shopify Integration (@weaverse/hydrogen):

export function combineWeaverseAndShopifySEO(
  weaverseSeo: PageSEOData,
  shopifySeo: SeoConfig
): SeoConfig {
  return {
    title: weaverseSeo.title || shopifySeo.title,
    description: weaverseSeo.description || shopifySeo.description,
    // Merge and prioritize Weaverse data over Shopify defaults
    ...shopifySeo,
    ...weaverseSeo
  }
}

Advanced Features:

  • JSON-LD structured data support
  • Multi-language SEO handling
  • Dynamic meta tag injection
  • SEO preview utilities for theme development

Theme Implementation Examples

React 19 Pattern (Recommended)

// app/routes/($locale).$.tsx - Custom pages
import { WeaverseSEO } from '@weaverse/react'

export default function CustomPage() {
  const { weaverseData } = useLoaderData<typeof loader>()
  
  return (
    <>
      <WeaverseSEO pageData={weaverseData} />
      <WeaverseContent data={weaverseData} />
    </>
  )
}

export async function loader({ context, params }: LoaderFunctionArgs) {
  const weaverseData = await context.weaverse.loadPage({
    type: "CUSTOM",
    handle: params["*"]
  })
  
  return { weaverseData }
}

React Router Meta Function Pattern (Legacy Support)

// app/routes/($locale).$.tsx - Custom pages
import { getWeaverseMeta } from '@weaverse/hydrogen'

export const meta: MetaFunction<typeof loader> = async ({ context, params }) => {
  return await getWeaverseMeta(context, {
    type: "CUSTOM", 
    handle: params["*"]
  })
}

export async function loader({ context, params }: LoaderFunctionArgs) {
  const weaverseData = await context.weaverse.loadPage({
    type: "CUSTOM",
    handle: params["*"]
  })
  
  return { weaverseData }
}

Hybrid Pattern (Best of Both)

// Support both patterns with adapter
import { MetaAdapter } from '@weaverse/hydrogen'

export const meta: MetaFunction<typeof loader> = ({ data }) => {
  return data.metaDescriptors
}

export default function CustomPage() {
  const { weaverseData, metaDescriptors } = useLoaderData<typeof loader>()
  
  return (
    <>
      <MetaAdapter descriptors={metaDescriptors} />
      <WeaverseContent data={weaverseData} />
    </>
  )
}

export async function loader({ context, params }: LoaderFunctionArgs) {
  const weaverseData = await context.weaverse.loadPage({
    type: "CUSTOM",
    handle: params["*"]
  })
  
  const metaDescriptors = formatMetaFunction(weaverseData.seo || {})
  
  return { weaverseData, metaDescriptors }
}

Package Updates Required

@weaverse/hydrogen

  • Extend HydrogenPageData interface with SEO fields
  • Update WeaverseClient.loadPage() to fetch SEO data
  • Add getPageMeta() method for React Router compatibility
  • Add formatMetaFunction() utility
  • Add getWeaverseMeta() helper function
  • Add combineWeaverseAndShopifySEO() utility

@weaverse/react

  • Create WeaverseSEO component for React 19 native meta
  • Add MetaAdapter component for backward compatibility
  • Add SEO-related TypeScript interfaces
  • Add validation utilities for SEO data

@weaverse/schema

  • Define PageSEOData interface
  • Add Zod schemas for SEO validation
  • Export SEO-related types for theme consumption

@weaverse/core (if needed)

  • Core SEO data structures
  • Framework-agnostic utilities

Testing Requirements

  1. Unit Tests

    • SEO data fetching and formatting
    • Meta tag generation accuracy
    • Component rendering with different SEO configurations
  2. Integration Tests

    • WeaverseClient SEO data loading
    • React 19 component meta tag hoisting
    • React Router meta function integration
  3. E2E Tests (Pilot Template)

    • Custom page SEO rendering
    • Meta tag presence in page source
    • Social media preview functionality

Migration Strategy

For Existing Themes

  1. Immediate: Themes can opt-in to SEO features via SDK updates
  2. Backward Compatible: Existing routes continue working without SEO
  3. Gradual Migration: Themes can migrate route-by-route to React 19 patterns
  4. Documentation: Provide migration guides for both React 19 and React Router patterns

For New Themes

  1. Default SEO Support: New theme scaffolds include SEO best practices
  2. React 19 First: Encourage native meta component usage
  3. Complete Examples: Pilot template demonstrates all patterns

Dependencies

  • Related Issue: Weaverse Builder UI for managing SEO metadata
  • API Requirements: Builder API must provide SEO data endpoints
  • React Compatibility: Support React 18+ and React 19
  • Framework Support: React Router v7, potential future Next.js support

Acceptance Criteria

  • HydrogenPageData includes optional SEO fields
  • WeaverseClient fetches and returns SEO metadata
  • React 19 WeaverseSEO component renders proper meta tags
  • React Router helper functions generate correct MetaDescriptor arrays
  • Backward compatibility maintained for existing themes
  • Comprehensive TypeScript support
  • Documentation with implementation examples
  • Pilot template demonstrates all usage patterns
  • Performance impact is minimal (lazy loading where appropriate)

Priority

High - This is essential for Weaverse themes to compete with other Shopify theme solutions that provide comprehensive SEO management capabilities.

Technical Notes

  • Package Versions: Maintain semver compatibility across @weaverse/* packages
  • Bundle Size: Keep SEO utilities tree-shakeable to minimize impact
  • Framework Support: Design with future framework support in mind (Next.js, etc.)
  • CDN Compatibility: Ensure meta tags work with CDN and edge caching
  • Accessibility: Include proper semantic markup in meta tag generation

Metadata

Metadata

Labels

enhancementNew feature or request

Projects

Status

Todo

Relationships

None yet

Development

No branches or pull requests

Issue actions