---
schema_version: "1"
id: 07003149cc6a91c43dbad785dbce13b3b58187164d76858ef2c67dd5d0ea2973
title: AEO and GEO
collection: developer
version: current
locale: en-us
portal_url: https://docs.salesforce.com/developer/current/en-us/commerce-cloud/sfnext/guides/sfnext-aeo-geo.md
canonical_url: https://developer.salesforce.com/docs/commerce/pwa-kit-managed-runtime/guide/sfnext-aeo-geo.html
source_site: developer.salesforce.com
content_type: documentation
content_hash: b19cdb264e7f93992f9dc214e645b1e161fa99996adf3998ed15429e79917310
products:
  - Composable Storefront
  - Salesforce APIs
---
# AEO and GEO

Storefront Next ships built-in support for Answer Engine Optimization (AEO) and Generative Engine Optimization (GEO) on the Product Detail Page (PDP) and Product Listing Page (PLP).

**AEO (Answer Engine Optimization)** makes product and category facts machine-readable and consistent so search engines and crawlers can parse and enrich results (for example, rich product snippets), and so assistants, voice search, and other answer-style surfaces can cite accurate who, what, how much, and in-stock information without guessing from unstructured HTML alone.

**GEO (Generative Engine Optimization)** supplies clear entity structure (products, offers, collections, breadcrumbs) and aligned metadata (titles, descriptions, canonical URLs) so generative and retrieval-augmented systems can ground responses in your storefront's authoritative data.

AEO and GEO have these benefits:

- Rich results in search engines: Structured `Product` data enables Google rich snippets: price, availability, and review stars inline in search results.
- AI citation and grounding: Clear entity graphs (products, offers, breadcrumbs) give Large Language Models (LLMs) and Retrieval-Augmented Generation (RAG) systems verifiable facts to cite rather than fabricated summaries.
- Voice and assistant surfaces: Answer engines can resolve who, what, how much, and in-stock queries directly from your structured data.
- Multisite consistency: URL helpers respect site/locale path prefixes and proxy headers, so structured data links are correct across all your storefronts without manual configuration. Managed Runtime and proxy hosts do not leak internal origins into structured data.
- Zero page-breakage risk: Schema generation runs in a deferred Promise. If it fails, the page renders normally with no script tag.

## How It Works

Two technical mechanisms work together: JSON-LD and `SeoMeta`.

| Mechanism                                       | Purpose                                                         | Where                                           |
| ----------------------------------------------- | --------------------------------------------------------------- | ----------------------------------------------- |
| JSON-LD (`<script type="application/ld+json">`) | Structured entity data for crawlers and AI systems              | PDP and PLP route loaders → `JsonLd` component  |
| `SeoMeta`                                       | Classic `<title>`, `<meta name="description">`, Open Graph tags | `ProductContent` (PDP) and category route (PLP) |

JSON-LD is the primary AEO and GEO signal. `SeoMeta` complements it—crawlers and social previews still read `<head>` tags even when structured data is present.

## Where It Is Implemented

| Concern            | PDP                                                              | PLP (category)                             |
| ------------------ | ---------------------------------------------------------------- | ------------------------------------------ |
| Route              | `src/routes/_app.product.$productId.tsx`                         | `src/routes/_app.category.$categoryId.tsx` |
| JSON-LD generator  | `src/utils/product-schema.ts`                                    | `src/utils/category-schema.ts`             |
| Public URL helpers | `src/utils/schema-url.ts`                                        | Same                                       |
| JSON-LD injection  | `src/components/json-ld/index.tsx`, script `id="product-schema"` | Same, `id="category-schema"`               |
| Page meta          | `SeoMeta` in `ProductContent`                                    | `SeoMeta` on category page                 |

## Product Detail Page (PDP)

### What Gets Emitted

The PDP emits a `schema.org/Product` graph with these fields populated from B2C Commerce API (SCAPI) data when available.

| Field                             | Source                                                                                                            |
| --------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `name`                            | `product.name`                                                                                                    |
| `description`                     | `product.longDescription`, falling back to `shortDescription`, then `pageDescription` if earlier fields are empty |
| `image`                           | Primary image; up to 5 large or medium image URLs when multiple views exist (thumbnails excluded)                 |
| `sku`, `productID`                | `product.id`                                                                                                      |
| `url`                             | Public storefront origin + current path                                                                           |
| `brand`                           | `product.brand`                                                                                                   |
| `mpn`                             | `product.manufacturerSKU`                                                                                         |
| `gtin`                            | `product.ean`                                                                                                     |
| `category`                        | `product.primaryCategoryId`                                                                                       |
| `color`                           | First `color` variation attribute value                                                                           |
| `additionalProperty`              | All variation attributes + custom attributes as `PropertyValue`                                                   |
| `offers.price`                    | Effective price (base or promo)                                                                                   |
| `offers.priceCurrency`            | `product.currency`                                                                                                |
| `offers.availability`             | Derived from inventory (`InStock`, `OutOfStock`, `BackOrder`, and `PreOrder`)                                     |
| `offers.priceValidUntil`          | One year from generation date (default horizon)                                                                   |
| `offers.lowPrice` and `highPrice` | Populated for master products with a price range across variants                                                  |

`SeoMeta` sets `<title>` to the product name, meta description to `pageDescription` or `shortDescription`, and Open Graph `og:type` to `product` with the page URL and primary image.

File:

```
In src/utils/product-schema.ts:
generateProductSchema(product, productUrl)
```

## Product Listing Page (PLP)

### What Gets Emitted

The PLP emits a `schema.org/CollectionPage` with a `mainEntity` of type `ItemList`.

`CollectionPage`

- `name`: Category name
- `description`: `category.pageDescription`, when present
- `url`: Canonical category URL

`ItemList` (inside `mainEntity`)

- `numberOfItems`: Total from search result when present
- `itemListElement`: Up to 24 products (for payload size), each as a `ListItem` wrapping a lightweight `Product` (name, url, image, offers)

#### Pricing

Each list item uses an effective price: the lowest promotional price when promotions exist, otherwise the base price. For master products, the schema considers variant-level prices when applicable.

#### Availability

The `orderable` field on the search hit drives `InStock` or `OutOfStock` availability when known. If the hit doesn't expose orderability but `config.search.products.refine.orderableOnly` is `true`, then availability is inferred as in stock for listed hits.

#### `BreadcrumbList`

Built from `parentCategoryTree` when present, plus the current category. Category links use `buildCategorySchemaUrl` so paths stay consistent with multi-site prefixes.

**note:** The loader merges critical and non-critical search hits before schema generation, so the `ItemList` reflects the full first page of results, not just the above-the-fold slice.

#### `SeoMeta`

Sets `<title>` to the category name, meta description to `pageDescription` or the general description, and `og:type` to `website` with the canonical page URL.

File:

```
In src/utils/category-schema.ts:
generateCategorySchema({ category, searchResult, pageUrl, defaultCurrency, config })
```

## URL Construction

Storefront Next builds all JSON-LD URLs from the public storefront origin and current path, not from `product.slugUrl` or internal API URLs. This approach is critical in serverless or proxied environments, such as Managed Runtime and content delivery networks (CDNs), where `request.url` can contain internal routing addresses.

File:

```
src/utils/schema-url.ts
```

Key functions:

```ts
// Reads x-forwarded-host / x-forwarded-proto headers to resolve the public domain
getPublicOrigin(request: Request): string

// Builds product and category URLs that preserve the site/locale path prefix
buildProductSchemaUrl({ productId, origin, currentPageUrl }): string | undefined
buildCategorySchemaUrl({ categoryId, origin, currentPageUrl }): string | undefined
```

### Example: Multisite URL Preservation

```ts
// Current page: https://example.com/global/en-GB/category/womens
// Built product URL:
buildProductSchemaUrl({
  productId: "12345",
  origin: "https://example.com",
  currentPageUrl: "https://example.com/global/en-GB/category/womens",
});
// → 'https://example.com/global/en-GB/product/12345'
```

Storefront Next extracts the site/locale prefix (for example, `/global/en-GB`) from the current page URL and prepends it to every linked URL inside the schema.

## Rendering

Both PDP and PLP inject JSON-LD via the `JsonLd` component, which renders a `<script type="application/ld+json">` tag. The loader defers schema generation and returns an unresolved Promise. `JsonLd` is wrapped in `<Suspense>` using React's `use()` so that JSON-LD can stream with Server-Side Rendering (SSR) after the product or category payload is ready.

```tsx
// JsonLd component usage (already wired in the routes)
import { JsonLd } from "@/components/json-ld";

<Suspense fallback={null}>
  <JsonLdWrapper schemaPromise={loaderData.productSchema} id="product-schema" />
</Suspense>;
```

If `generateProductSchema` or `generateCategorySchema` throws, failures in schema generation are logged and result in no script tag rather than breaking the page. The page continues to load normally.

Script IDs:

- PDP: `id="product-schema"`
- PLP: `id="category-schema"`

## Tests and Stories

- Unit tests: `src/utils/category-schema.test.ts`, `src/components/json-ld/index.test.tsx`
- Route-level coverage touches JSON-LD in `src/routes/_app.category.$categoryId.test.tsx` and `src/routes/_app.product.$productId.test.tsx`
- Storybook: `src/components/json-ld/stories/index.stories.tsx` (includes ItemList-oriented examples)

## Customizing the Schema

### Add Aggregate Ratings (PDP)

The `ProductSchema` type already includes an `aggregateRating` field. Wire it in `product-schema.ts` after fetching rating data from your review provider:

```ts
schema.aggregateRating = {
  "@type": "AggregateRating",
  ratingValue: "4.5",
  reviewCount: "128",
  bestRating: "5",
  worstRating: "1",
};
```

### Add FAQ Structured Data

If your PDP or editorial pages include FAQ content, add a `FAQPage` or `QAPage` schema alongside the product schema:

```tsx
<JsonLd
  id="faq-schema"
  data={{
    "@context": "https://schema.org",
    "@type": "FAQPage",
    mainEntity: [
      {
        "@type": "Question",
        name: "What sizes are available?",
        acceptedAnswer: {
          "@type": "Answer",
          text: "Available in XS–XXL.",
        },
      },
    ],
  }}
/>
```

### Enrich Catalog Fields

The quality of AEO/GEO signals depends directly on the data in your B2C Commerce catalog. Ensure Business Manager catalog fields are populated so generators and answer engines have factual text to align with structured data. Make sure that these Business Manager fields are populated for every product and category.

- `pageDescription`: Used as the meta description and JSON-LD description
- `longDescription` or `shortDescription`: Fallbacks for JSON-LD description
- `brand`: Populates `schema:brand`
- `ean`: Populates `schema:gtin`
- `manufacturerSKU`: Populates `schema:mpn`

**important:** Google's Rich Results Test flags structured data that contradicts visible on-page content as a quality issue. Keep JSON-LD in sync with visible on-page content to avoid conflicting signals. Keep catalog fields in sync with what shoppers see.

## Validation

After making changes, validate with:

- [Google Rich Results Test](https://search.google.com/test/rich-results): Checks eligibility for rich snippets
- [Schema.org Validator](https://validator.schema.org): Validates against the schema.org spec

Keep JSON-LD in sync with visible on-page content to avoid conflicting signals.

**note:** In development, `JsonLd` logs errors to the console when invalid data is passed. These errors are suppressed in production.

## See Also

- [SEO and Metadata](https://developer.salesforce.com/docs/commerce/pwa-kit-managed-runtime/guide/sfnext-seo-metadata.md): Canonical URLs, `hreflang`, full `SeoMeta` prop reference, query-parameter allowlists
- [Images](https://developer.salesforce.com/docs/commerce/pwa-kit-managed-runtime/guide/sfnext-images.md): Image URLs and alt text
