Rethinking the Shop App’s Store Screen
Shopify’s Shop Store team is responsible for the buyer experience on the Shop App’s Store Screen—the place where customers browse a merchant’s products and collections. The team recently moved this screen to a server-driven UI architecture, shifting layout and content decisions from the client to the backend. The change lets the team customize merchant storefronts dynamically and ship changes without waiting for app releases.
Previously, the Store Screen was fully client-driven: the mobile app handled layout, visual design, and component selection. That approach meant every merchant’s store looked essentially the same, regardless of store size or catalog depth. A store with hundreds of products might benefit from a “Best Sellers” section, but for a store with only a handful of items, that section would just duplicate the main product list.
The static nature of client-driven UIs also slowed down experimentation. The team was limited by a weekly release cycle, meaning any new test or layout change could only go out once per week. Bugs discovered post-release were equally slow to fix—a rendering issue discovered after a release could take up to seven days to reach users. Server-driven UI removes those constraints: backend changes take effect immediately, experiments can be launched or rolled back at any time, and problematic sections can be hidden remotely while fixes are prepared for the next release.
How the Architecture Works
In the server-driven model, the backend tells the client exactly which sections to render—for example “Best Sellers,” “New Arrivals,” or “All Products”—and which layout to use, such as a grid or a scrollable shelf. This enables templated store layouts that are personalized to each merchant’s needs.
The server-side implementation is split into several layers:
The server-side architecture for server-driven UI.
- Template Processing Layer: Contains hard-coded default templates and merchant-specific customizations pulled from the Merchant Database. The Template Metadata Reader parses these templates and returns the list of sections to render.
- Data Loading Layer: Creates a
SectionDataLoaderobject for each section. Each object encapsulates the logic for fetching that section’s data. - GraphQL Layer: Maps
SectionDataLoaderobjects to GraphQL types and resolves the fields required by the Shop App. - Orchestration Layer: Coordinates the flow between the template, data loading, and GraphQL layers, passing the necessary information between them.
On the API side, a store sections query returns a list of sections for a given shop. Each section inherits from a base Section type and can be further specialized as a ProductsSection or CollectionsSection. The former handles product lists like “All Products” or “Best Sellers”; the latter handles collections. Layouts are also configurable: GridLayout supports a 2x2 grid with large product blocks or a 3x3 grid with medium blocks, while ShelfLayout allows horizontal scrolling with small or large product blocks. Keeping layouts as a property of a section means new display formats can be introduced without creating entirely new section types.
Client-Side Component Structure
The client uses a layered component architecture to render what the server sends back:
At the top level, the ServerDrivenStoreScreen component is the entry point when a buyer navigates to a merchant’s store. It renders the store header, navigation bar, search bar, and, for each section returned by the server, a StoreSectionContainer component.
That container examines the section type and decides what to render. For a product section, it instantiated a ProductsSection, which in turn chooses between ProductGrid and ProductShelf based on the display type from the server. The ProductsSection also passes a callback that navigates to the product details page when a product is tapped. For a collections section, a CollectionsSection renders a CollectionShelf, with a callback that opens the collection details screen.
Data fetching happens through two GraphQL calls. The first is a StoreSections query, kicked off in the ServerDrivenStoreScreen via a useStoreSections hook; the results are passed down to child components. The second is an existing ShopInfo query, prefetched from cache using a useStoreInformation hook to populate the store header.
Ownership Across the Stack
As a Dev Degree intern, my client work centered on building the ProductGrid, ProductsSection, and StoreSectionContainer components, along with the StoreSections query and the useStoreSections hook. Touching components across multiple layers of the architecture gave me a working understanding of how the Shop client fits together, and let me carry the React experience from earlier Dev Degree placements into a production setting.
Beyond implementation, I was involved in key architectural decisions — most notably whether to reuse an existing team’s server-driven UI components or build Shop Store-specific ones. After weighing the pros and cons with my team, we settled on creating our own components when the situation called for it. That gave us control over the data types in each section while still permitting reuse of any components that could be cleanly decoupled.
Handling Unknown Layout Types
A second challenge was deciding how to support layout and section types that older client versions don’t recognize. We approached it the same way, building a pros and cons list from both the back-end and client-side perspectives before deciding. In the end, the responsibility fell to the client: it has to interpret whatever the query returns. Each section defines a default layout, and that fallback is what renders if the client receives a layout type it doesn’t understand.
The Payoff for Merchants and Teams
Taking a server-driven approach puts merchants in control of how their store is personalized. It also lets Shop tailor the experience for new and returning buyers individually, which opens the door to learning what actually engages shoppers in different contexts. The architecture adds practical capabilities on top of that: running different experiments across templates simultaneously, and updating the buyer-facing UI without shipping a new app version. As a development team, that means we have a system that can evolve with the product. The hope is that this pattern can be adopted more broadly by teams across Shop.
I’d like to thank the Shop Store development team for their technical guidance and support throughout the project. Leading the client work as an intern gave me the chance to take on more responsibility, sharpen my technical skills, and practice project management. It’s made me a better developer, and I’m looking forward to applying what I learned both in future Shopify placements and in my computer science studies.



