structured cleanly in a Hyvä theme
Rewriting the same product fields in every query does not just bloat the source code, it bloats every single request sent to the server. Fragment composition keeps GraphQL queries in a Hyvä frontend lean, consistent, and maintainable across Magento upgrades.
Table of Contents
- 1. The problem: duplicated fields in every product query
- 2. GraphQL fragment basics, briefly revisited
- 3. Layered fragment composition: price, card, and detail fragments
- 4. Organizing fragments centrally instead of inline in every query file
- 5. Reducing query size and network overhead through fragments
- 6. Fragments and client-side caching in the Hyvä frontend
- 7. Versioning fragments across schema changes between Magento upgrades
- 8. Testing and schema validation for fragments in the CI pipeline
- 9. Checklist for a maintainable fragment structure
- 10. Summary
- 11. FAQ
1. The problem: duplicated fields in every product query
In a Hyvä theme that has grown over time, the product list, cart, wishlist, and cross-sell slider almost always request the same basic fields: SKU, name, image, price range, and URL key. When those fields are spelled out individually in every query file, every new call site adds another risk that a change gets applied in one place and forgotten everywhere else.
The consequences usually only show up in production: a special price missing from the wishlist because its query was never updated to include special_price, or price formatting that drifts slightly between the category page and the cart because both were maintained independently. Maintenance cost does not scale with the number of fields, it scales with the number of places where the same fields were retyped.
2. GraphQL fragment basics, briefly revisited
A named fragment defines a field selection once for a given type and can then be spread into as many queries as needed. For Magento that means concretely: a fragment called ProductCardFields on ProductInterface describes exactly the fields a product card needs, and every query that renders a product card spreads that one fragment instead of declaring the fields again.
Magento's GraphQL schema supports fragments like any spec-compliant GraphQL server, with no special handling required. That works regardless of whether the Hyvä frontend uses a lean fetch-based GraphQL client or a more elaborate library with query caching, because fragments are pure query-document syntax that gets assembled into a complete query before it is ever sent.
fragment ProductCardFields on ProductInterface {
sku
name
url_key
small_image {
url
label
}
price_range {
minimum_price {
regular_price { value currency }
final_price { value currency }
}
}
}
query CategoryProducts($categoryId: String!) {
products(filter: { category_id: { eq: $categoryId } }) {
items {
...ProductCardFields
}
}
}
query WishlistItems($wishlistId: ID!) {
wishlist(id: $wishlistId) {
items_v2 {
items {
product {
...ProductCardFields
}
}
}
}
}
3. Layered fragment composition: price, card, and detail fragments
In practice, a layered structure pays off over one single large fragment: a small ProductPriceFields fragment encapsulates only the pricing logic, ProductCardFields spreads that price fragment and adds image and name, and ProductDetailFields in turn builds on ProductCardFields and adds description plus customer-specific attributes. If a price field changes, say because a new tier-price field gets added, one change in exactly one place takes effect everywhere prices are displayed.
This layering deliberately mirrors the structure of the Hyvä templates: a price.phtml partial renders the same fields the price fragment supplies, and a product-card.phtml spreads that partial, just as the corresponding query spreads the price fragment. When template boundaries and fragment boundaries line up, both can be reasoned about together instead of maintaining separate mental models for markup and data fetching.
fragment ProductPriceFields on ProductInterface {
price_range {
minimum_price {
regular_price { value currency }
final_price { value currency }
discount { percent_off }
}
}
}
fragment ProductCardFields on ProductInterface {
sku
name
url_key
small_image { url label }
...ProductPriceFields
}
fragment ProductDetailFields on ProductInterface {
...ProductCardFields
description { html }
meta_description
categories { name url_path }
}
4. Organizing fragments centrally instead of inline in every query file
Instead of redefining fragments in every query file, a dedicated directory for reusable fragments, from which every query imports exactly what it needs, tends to work well. The Hyvä GraphQL client assembles the final query from the main document and the imported fragments, so exactly one complete request goes to the server in the end, while the source code itself stays modular and individual fragments can be tested independently.
One pitfall here is naming collisions: if two teams independently define a fragment called ProductFields with different contents, assembling the query produces a conflict that only shows up as a cryptic runtime error. A feature-prefixed naming convention, such as PlpProductCardFields versus CheckoutProductFields, reliably prevents such collisions even when several teams work on different areas of the theme in parallel.
// app/design/frontend/Vendor/hyva-child/web/js/graphql/fragments/product-card.js
export const PLP_PRODUCT_CARD_FIELDS = /* GraphQL */ `
fragment PlpProductCardFields on ProductInterface {
sku
name
url_key
small_image { url label }
}
`;
// app/design/frontend/Vendor/hyva-child/web/js/graphql/queries/category-products.js
import { PLP_PRODUCT_CARD_FIELDS } from '../fragments/product-card';
export const CATEGORY_PRODUCTS_QUERY = /* GraphQL */ `
${PLP_PRODUCT_CARD_FIELDS}
query CategoryProducts($categoryId: String!) {
products(filter: { category_id: { eq: $categoryId } }) {
items { ...PlpProductCardFields }
}
}
`;
5. Reducing query size and network overhead through fragments
Fragment reuse pays off primarily not through better compression, but through the fact that every single query contains only the fields that particular view actually needs. A wishlist that spreads only the lean ProductCardFields fragment transfers noticeably less data than a query that accidentally drags along the full ProductDetailFields fragment with description and category tree, just because it already existed somewhere else.
With persisted queries, fragment discipline pays off further: a manageable, clearly layered set of query documents is far easier to check and maintain against a server-side allow-list than dozens of nearly identical queries that grew independently and quietly drifted apart over time.
6. Fragments and client-side caching in the Hyvä frontend
Normalized client-side caching depends on the same entity, say a product with a given SKU, coming back with the same field shape at every call site. Fragments guarantee exactly that: if the same ProductCardFields fragment is used everywhere, cache entries for the same SKU can be merged losslessly, instead of the card, wishlist, and cart each storing a slightly different partial slice of the same entity in the cache.
In a Hyvä frontend, a cache like that can be implemented pragmatically as an Alpine store that indexes products by SKU and checks, on read, whether a complete entry already exists before triggering a new request. Because the field shape is guaranteed consistent by the fragment, a simple object merge is enough, without needing a more elaborate normalization library.
// web/js/graphql/product-cache.js
const productCache = {};
export function mergeProduct(product) {
productCache[product.sku] = { ...productCache[product.sku], ...product };
return productCache[product.sku];
}
export function getCachedProduct(sku) {
return productCache[sku] || null;
}
7. Versioning fragments across schema changes between Magento upgrades
Magento minor releases occasionally shift fields in the GraphQL schema, for instance when the price_range structure gets extended with a new tier-price field, or a previously used field is marked deprecated and later removed. If all product fields live in a handful of central, layered fragments, the fix happens in exactly that one place instead of hunting through dozens of files for scattered occurrences.
For larger breaking changes, a temporary parallel fragment version, such as ProductCardFieldsV2 alongside the existing ProductCardFields, is worth introducing, so individual views can be migrated and tested one at a time before the old version is removed. A short comment block at the top of every fragment file documenting the minimum supported Magento version saves the tedious work of figuring out when a given field was even introduced during future upgrades.
# Compatible from Magento 2.4.6, price_range.minimum_price without tier_prices
fragment ProductCardFields on ProductInterface {
sku
name
price_range {
minimum_price {
regular_price { value currency }
final_price { value currency }
}
}
}
# From Magento 2.4.8, adds price_tiers for tier price display
fragment ProductCardFieldsV2 on ProductInterface {
sku
name
price_range {
minimum_price {
regular_price { value currency }
final_price { value currency }
}
}
price_tiers {
quantity
final_price { value currency }
}
}
8. Testing and schema validation for fragments in the CI pipeline
An introspection query against your own Magento instance returns the full schema actually available right now, as a reference. A small script that checks every field selection in your own fragments against that introspected schema catches breaking changes before a Composer update of magento/module-graph-ql lands untested in production and only surfaces through broken responses.
If that check runs as its own step in the CI pipeline before every deployment, the build fails reliably the moment a fragment references a field that no longer exists in the target schema. That turns a potential production bug into a clearly readable error message inside the pull request, long before a customer ever sees a broken product card.
9. Checklist for a maintainable fragment structure
Fragment composition is not an end in itself, it pays off mainly when the team consistently follows a clear convention for location, naming, and layering. A single undisciplined, ever-growing mega-fragment that accidentally contains every field ever needed causes the same problems as having no fragments at all, just concentrated in one place that is even harder to untangle.
The overview below ranks the measures discussed in this article by impact and implementation effort, so a team can focus first on the points that promise the biggest effect for a manageable amount of work.
| Measure | Impact | Implementation Effort | Risk of Doing Nothing |
|---|---|---|---|
| Layered fragments instead of a mega-fragment | Very high | Medium, one-time restructuring | Unnecessarily large queries at every call site |
| Central fragment directory with imports | High | Low to medium | Duplicated field lists that slowly drift apart |
| Feature-prefixed naming convention | Medium | Low | Naming collisions between teams when assembling queries |
| Fragment cache in the Alpine store | Medium | Medium | Redundant requests for already-loaded products |
| Versioned fragments for breaking changes | High | Medium, during the migration window | A big-bang migration instead of a gradual rollout |
| Schema validation in the CI pipeline | Very high | Low, a one-time script | Breaking changes only surface live through customer reports |
Mironsoft
Hyvä theme development and Luma migration
Still running Luma, or a Hyvä theme that just doesn't feel right?
We build Hyvä themes for Magento from scratch or migrate existing Luma shops cleanly, with Tailwind CSS, Alpine.js, and none of the unnecessary JavaScript baggage.
Luma-to-Hyvä Migration
Move an existing shop to Hyvä in a structured way, without losing functionality.
Custom Theme Development
Build a custom Hyvä theme from scratch based on your design.
Performance Optimization
Improve Core Web Vitals and load times in the Hyvä frontend with purpose.
10. Summary
GraphQL Fragments in Hyvä
Core problem
Duplicated product fields across many query files cause inconsistencies and high maintenance cost.
Solution
Layered, centrally organized fragments with a clear, feature-prefixed naming convention.
Caching effect
Consistent field shape from fragments enables lossless merging of product data in the client cache.
Upgrade safety
Versioned fragments and schema validation in the CI pipeline catch breaking changes before deployment.