KOMERO SEO reference

The KOMERO storefront exposes every SEO surface a merchant would expect from WooCommerce+Yoast, packaged as first-party functionality. Metadata is stored in the tenant configuration and product/category records as LocalizedText, resolved through the i18nPageMetadataAsync helper in lib/seo.ts. JSON-LD builders (buildProductJsonLd, buildOrganizationJsonLd, buildBreadcrumbJsonLd, buildWebsiteJsonLd, buildFaqJsonLd, buildItemListJsonLd) emit valid schema.org markup with per-tenant configuration for organizationType, foundingDate, areaServed, and priceRange. Sitemap.xml is generated with force-dynamic and one-hour ISR revalidation and includes image entries. Robots.txt is generated per host with staging protection and an AI crawler allow-list. Hreflang and x-default alternates are emitted automatically for every locale on every route via buildI18nUrls. Analytics scripts are injected through the consent-aware AnalyticsScripts component.

Developer reference

SEO capabilities reference

Every SEO surface exposed by the KOMERO storefront, mapped to the underlying types, admin location, and generated markup.

Metadata (title, description, keywords)

Product, category, and blog post entities each expose metaTitle, metaDescription, and metaKeywords as LocalizedText fields. Values are resolved at request time through i18nPageMetadataAsync in lib/seo.ts and returned via Next.js generateMetadata. When a tenant leaves a field blank, KOMERO falls back to the sensible default (product name / category name / blog title). Empty descriptions fall back to the first 160 characters of the entity description with entity references stripped.

// types/product.ts
export type ProductModel = {
  metaTitle?: LocalizedText;
  metaDescription?: LocalizedText;
  metaKeywords?: LocalizedText;
  canonicalOverride?: string;
  slug: string;
  isIndexable: boolean;
  // ...
};

Canonical URL resolution

Canonical URLs are resolved in this order: product.canonicalOverride (validated URL) → siteConfig.seo.canonicalBaseUrl + path + locale → siteConfig.url + path + locale. Category and dynamic pages currently only support the tenant-wide base override; per-page override is on the roadmap.

// lib/seo.ts
function getBASE(seo: SEOConfig, siteUrl: string) {
  return seo.canonicalBaseUrl?.trim() || siteUrl;
}

JSON-LD schemas

The storefront emits Product, Organization (with ContactPoint / PostalAddress / GeoCoordinates / OpeningHoursSpecification), BreadcrumbList, Website (with SearchAction), BlogPosting, ItemList, and FAQPage. AggregateRating structure is present in buildProductJsonLd but not populated until the ratings backend ships.

// lib/seo.ts
export function buildProductJsonLd(product, siteConfig, locale) {
  return {
    '@context': 'https://schema.org',
    '@type': 'Product',
    name, description, image, brand,
    offers: { '@type': 'Offer', price, priceCurrency, availability, url },
    // aggregateRating?: { ratingValue, reviewCount } // ← ratings roadmap
  };
}

Sitemap.xml

Generated in app/sitemap.ts with force-dynamic + revalidate = 3600. Per-host and per-tenant. Includes products (only when isIndexable is true), categories, brands, blog posts, blog categories, blog tags, dynamic pages, and static info pages. Product entries include an <image:image> child with the cover image URL. Invalid or pre-2000 sentinel dates are rejected.

// app/sitemap.ts
export const dynamic = 'force-dynamic';
export const revalidate = 3600;
export default async function sitemap() {
  // fetch tenant, products, categories, blog...
  return entries;
}

Robots.txt (AI crawler policy)

app/robots.ts is force-dynamic and per-host. Production hosts allow-list GPTBot, OAI-SearchBot, ChatGPT-User, ClaudeBot, and PerplexityBot for AI search visibility. Training-only crawlers (CCBot, anthropic-ai, Bytespider) are blocked. Staging hosts return Disallow: / for every user agent to protect draft storefronts from indexing.

// app/robots.ts (production)
User-agent: GPTBot
Allow: /

User-agent: ClaudeBot
Allow: /

User-agent: CCBot
Disallow: /

Hreflang & x-default

buildI18nUrls returns { canonical, languages } for a path across every active tenant locale. x-default is set to the tenant's fallback locale (usually ka for Georgian merchants, en for international). Emitted by Next.js as <link rel='alternate' hreflang='...' href='...'>.

// lib/seo.ts
const { canonical, languages } = buildI18nUrls('/product/xyz', activeLocales, siteConfig);
return { alternates: { canonical, languages } };

Open Graph & Twitter Card

OG tags (og:title, og:description, og:image, og:type, og:locale, og:site_name) are generated per page from the entity plus tenant SEOConfig defaults. og:type is 'product' on product pages and 'website' elsewhere. Twitter card type defaults to summary_large_image and auto-downgrades to summary when the image is smaller than 1200×630. twitter:site and twitter:creator are configurable per tenant.

// lib/seo.ts
const card = resolveTwitterCard(image.width, image.height);
// -> 'summary_large_image' or 'summary'

Image alt text

ProductImageModel.altText is LocalizedText, so each active locale can have its own alt text per image. Managed in the admin ReviewImagesModal, which supports drag-to-reorder, cover selection, deletion + undo, paste upload, and image compression. Alt text is never auto-generated - tenants provide it manually.

// types/product.ts
export type ProductImageModel = {
  imagePath: string;
  altText: LocalizedText;
  isCover: boolean;
  order: number;
};

Analytics script injection

GA4, GTM, Meta Pixel, Hotjar, and Microsoft Clarity IDs are stored in tenant SEOConfig and injected via AnalyticsScripts.tsx. Every script is wrapped in a consent gate that respects the cookie consent categories (analytics vs marketing). Arbitrary <script> injection is deliberately not exposed for security and performance reasons.

// components/marketing/analytics/AnalyticsScripts.tsx
if (consent.analytics && seo.googleAnalyticsId) {
  loadGA4(seo.googleAnalyticsId);
}

AI-generated metadata (Gemini)

The /api/ai/generate-seo endpoint calls Google Gemini via the @ai-sdk/google Vercel AI SDK to draft metaTitle, metaDescription, and metaKeywords for a product, category, or blog post based on the entity name, description, and locale. Drafts are inserted directly into the admin form for the tenant to review and save.

// api/ai/generate-seo
POST { entityType, entityId, locale }
=> { metaTitle, metaDescription, metaKeywords }
SEO Capabilities Reference | KOMERO Docs