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 }
}
}
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.