Skip to main content

Page model

Evolve uses two complementary page systems: CMS-managed pages for content-driven experiences and catalog pages for category browsing driven by the commerce backend.

CMS-managed pages

CMS-managed pages are created and maintained in the CMS (Contentful or Storyblok). They share a common interface and are returned through a Page union type:

interface AbstractPage {
id: ID!
name: String!
path: String!
translatedPaths: [TranslatedPath!]
breadcrumbs: [Breadcrumb!]
meta: PageMeta
}

union Page = ContentPage | CatalogPage

ContentPage

A free-form content page composed of blocks managed in the CMS. Use this for pages like "About us", landing pages, or editorial content.

type ContentPage implements AbstractPage @key(fields: "id") {
id: ID!
name: String!
path: String!
translatedPaths: [TranslatedPath!]
breadcrumbs: [Breadcrumb!]
meta: PageMeta
hero: HeroBlock
body: [Block]
}

Query a content page by path:

query GetContentPage($path: String!) {
contentPage(path: $path) {
name
meta { title, description }
breadcrumbs { name, path, type }
hero { ... }
body { ... }
}
}

CatalogPage

A product listing page where the content and product selection are defined in the CMS. The CMS service owns the page content (hero, top/bottom content sections, teasers), while the catalog service extends it with product listing configuration through GraphQL Federation.

# CMS service owns the content fields
type CatalogPage implements AbstractPage @key(fields: "id") {
id: ID!
name: String!
path: String!
translatedPaths: [TranslatedPath!]
breadcrumbs: [Breadcrumb!]
meta: PageMeta
hero: HeroBlock
topContent: [Block!]
bottomContent: [Block!]
categoryId: String
teaserRow: TeasersBlock
}

# Catalog service extends with product listing config
type CatalogPage @extends @key(fields: "id") {
id: ID!
categoryId: String @external
productListingConfig: ProductListingConfig!
@requires(fields: "categoryId")
}

This is a good example of how GraphQL Federation works in Evolve: the CMS service resolves the content fields, the catalog service resolves productListingConfig by reading the categoryId that the CMS provides, and the gateway merges both into a single response.

Query a catalog page by path:

query GetCatalogPage($path: String!) {
catalogPage(path: $path) {
name
categoryId
meta { title, description }
breadcrumbs { name, path, type }
productListingConfig {
categoryId
defaultOrder
prefilters { key, value }
}
topContent { ... }
bottomContent { ... }
}
}

Generic page query

When you don't know the page type ahead of time, use the page query which returns the Page union:

query GetPage($path: String!) {
page(path: $path) {
... on ContentPage {
name
body { ... }
}
... on CatalogPage {
name
productListingConfig { categoryId }
}
}
}

All three CMS page queries (contentPage, catalogPage, page) accept optional version and previewID arguments for draft/preview content.

Catalog pages

Catalog pages are resolved directly from the commerce backend without CMS involvement. They are standalone types (not part of the Page union) and are owned entirely by the catalog service.

ProductCategoryPage

Renders all products in a specific product category. The category hierarchy in the commerce backend is the source of truth.

type ProductCategoryPage {
meta: PageMeta
path: String!
name: String
indexable: Boolean
category: ProductCategory!
productListingConfig: ProductListingConfig!
breadcrumbs: [Breadcrumb!]!
}

Query by path:

query {
productCategoryPageByPath(path: "/shoes/running") {
name
category { name, slug }
productListingConfig {
categoryId
defaultOrder
enabledFacets
}
breadcrumbs { name, path }
}
}
info

Use CatalogPage when content editors need control over the page layout (hero images, editorial content blocks, teaser rows). Use ProductCategoryPage when the category structure in the commerce backend should drive the page directly.

CategoryOverviewPage

Displays child categories for a parent category, useful for top-level navigation pages.

type CategoryOverviewPage {
meta: PageMeta
path: String!
name: String
indexable: Boolean
category: ProductCategory
childCategories: [ProductCategory!]!
breadcrumbs: [Breadcrumb!]!
}

Product detail pages

Product detail pages don't have a dedicated page type. The frontend queries the product field directly:

query GetProductDetailPage($slug: String!) {
product(slug: $slug) {
name
brand
slug
meta { title, description }
breadcrumbs { name, path, type }
variant(sku: $selectedSku) {
sku
availability
description
price {
gross { centAmount, currencyCode }
net { centAmount, currencyCode }
}
images { url, alt }
}
}
}

Search pages

Search pages also don't have a dedicated page type. The frontend uses the productSearch query from the catalog service:

query SearchProducts(
$searchTerm: String
$page: Int!
$pageSize: Int!
$sort: ProductSortOrder!
$filters: [FacetFilterInput!]
) {
productSearch(
searchTerm: $searchTerm
page: $page
pageSize: $pageSize
sort: $sort
filters: $filters
) {
total
items { ... }
facets { ... }
}
}

Shared types

Several types are shared across page types using the @shareable federation directive:

type PageMeta @shareable {
title: String
description: String
keywords: [String]
openGraph: OpenGraph
}

type Breadcrumb @shareable {
type: BreadcrumbType!
name: String!
path: String!
}

enum BreadcrumbType {
CATEGORY
CONTENT
PRODUCT
}

Both the CMS and catalog services resolve PageMeta and Breadcrumb independently, with the @shareable directive telling the gateway that either service can provide these types.