build people and products
Copyright 2026 BILDIT, INC. All Rights Reserved
news
By BILDIT • May 26, 2026 • 17 min read
A step-by-step technical deep-dive into migrating a Shopify Plus storefront to a composable architecture, and why the stores that make this move are quietly pulling ahead on both speed and revenue.
There's a moment every eCommerce operator dreads. You open Lighthouse on a key product page and the mobile score is clearly lagging. The exact number matters less than the pattern: pages are too heavy, rendering is too slow, and the experience feels delayed.
This isn't a knock on Shopify Plus. It's an extraordinary commerce platform, the backend is genuinely world-class. But the frontend that ships with it was designed for simplicity, not for the kind of obsessive performance optimization that modern eCommerce demands. And in 2026, that gap between "good enough" and "great" is measured in dollars.
Faster mobile storefronts tend to keep users engaged longer and reduce the friction that often shows up before a shopper ever reaches checkout.
The solution is not to abandon Shopify. It is to decouple the frontend, rebuild the experience layer with Next.js, and give marketers a faster way to manage and ship content through BILDIT.
Before we get into the migration itself, it's worth understanding why this problem exists because the constraints are architectural, not accidental.
Shopify's standard storefronts run on Liquid, a templating language that renders pages server-side. Every time a user hits a product page, Shopify's servers process the Liquid template, pull data from its database, assemble the HTML, and send it down the wire. The result is a fully rendered page, but one that's been assembled dynamically on every single request.
This creates several compounding performance problems. First, there's no meaningful way to pre-render pages at build time and serve them as static HTML from a CDN edge. Second, Liquid templates tend to accumulate bloat over time: third-party app scripts, analytics pixels, chat widgets, and custom tracking tags that each add render-blocking weight. Third, image handling is limited. Shopify does offer some image optimization, but you have no control over format conversion, responsive srcsets, or the kind of aggressive lazy-loading that Next.js's <Image /> component delivers automatically.
Core Web Vitals are a useful way to show why storefront performance work matters, especially on image-heavy commerce pages. Use your own CrUX and PageSpeed data to establish the baseline rather than relying on a single industry-wide percentage.
The headless approach flips this entirely. Shopify Plus continues to handle what it does best: inventory management, order processing, payment security, and the checkout flow. But the storefront itself is built as a completely independent Next.js application, talking to Shopify through its Storefront GraphQL API. The result is a frontend you have total control over and total responsibility for optimizing.
Before we write any code, let's map out the architecture. Understanding the data flow is critical, because a headless setup has more moving parts than a traditional theme and each one needs to be configured correctly.
Shopify Plus remains the commerce engine. It owns your product catalog, inventory, customer accounts, cart state, and checkout. None of that changes.
Shopify Storefront API is the GraphQL interface that exposes Shopify's data to external applications. It's how your Next.js frontend will query products, collections, and cart state. It's also how it will create and update carts, initiate checkout, and handle customer authentication. Shopify's Storefront API is rate-limited at roughly 1,000 points per minute per store, and a single complex query typically costs 10–50 points, so efficient query design matters.
Next.js is the frontend framework. It handles rendering strategy (static generation, server-side rendering, incremental static regeneration), routing, image optimization, and deployment. It's the layer where performance lives or dies.
BILDIT is the Visual Experience Engine that manages your marketing content: landing pages, promotional banners, editorial copy, and personalized content blocks. It connects to your Next.js frontend via API, delivering structured content that your React components render. BILDIT's Shopify Plus integration means product data and marketing content stay in sync without custom glue code.
Vercel is where it all runs. Next.js was built by Vercel, and the platform's edge network is purpose-built for the kind of performance this architecture demands: global CDN, edge functions, automatic static optimization, and seamless ISR support.
Think of it as a pipeline: Shopify Plus → Storefront API → Next.js (on Vercel) ← BILDIT. Data flows in from two sources, commerce data from Shopify, content data from BILDIT, and Next.js assembles them into a single, fast, SEO-friendly storefront.
This is where it starts. Open your terminal and scaffold a new Next.js application with TypeScript and Tailwind CSS, the standard setup for a production-grade eCommerce frontend.
npx create-next-app@latest shopify-headless --typescript –tailwind
cd shopify-headless
Once that's initialized, create a .env.local file at the project root. This is where your secrets live, never commit this file.
# .env.local
SHOPIFY_STORE_DOMAIN=your-store.myshopify.com
SHOPIFY_STOREFRONT_ACCESS_TOKEN=your_token_here
BILDIT_API_URL=https://api.bildit.co/v1
BILDIT_API_KEY=your_bildit_key_here
npm i -g vercel
vercel login
vercel --prod
Vercel will detect your Next.js project automatically and configure the build pipeline. Add your environment variables in the Vercel dashboard under Settings → Environment Variables. This keeps credentials out of your codebase entirely.
At this point, you have a blank Next.js canvas deployed to Vercel's edge network. The real work starts now.
This is the plumbing. Everything your storefront needs from Shopify (products, collections, pricing, cart state) flows through here.
First, you need to enable the Headless sales channel in your Shopify admin. Navigate to Settings → Apps and sales channels, install the Headless channel from the Shopify App Store, and generate a Storefront API access token with the permissions your app needs (read access to products, collections, and customers at minimum; read/write access to carts).
// lib/shopify.ts
const SHOPIFY_STORE_DOMAIN = process.env.SHOPIFY_STORE_DOMAIN!;
const SHOPIFY_STOREFRONT_TOKEN = process.env.SHOPIFY_STOREFRONT_ACCESS_TOKEN!;
export async function shopifyFetch<T>(query: string, variables?: object): Promise<T> {
const response = await fetch(
`https://${SHOPIFY_STORE_DOMAIN}/api/2024-01/graphql.json`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Shopify-Storefront-Access-Token': SHOPIFY_STOREFRONT_TOKEN,
},
body: JSON.stringify({ query, variables }),
}
);
if (!response.ok) {
throw new Error(`Shopify API error: ${response.status}`);
}
const json = await response.json();
if (json.errors) {
throw new Error(JSON.stringify(json.errors));
}
return json.data as T;
}
This is your single entry point for all Shopify data. Every product query, every cart mutation, every collection fetch, it all goes through here. The function is intentionally simple: it sends a GraphQL query, handles errors, and returns typed data. No unnecessary abstraction layers.
// lib/shopify/queries.ts
export const GET_PRODUCTS = `
query GetProducts($first: Int!) {
products(first: $first) {
edges {
node {
id
title
handle
descriptionHtml
priceRange {
minVariantPrice {
amount
currencyCode
}
}
featuredImage {
url
altText
width
height
}
variants(first: 5) {
edges {
node {
id
title
price {
amount
currencyCode
}
availableForSale
}
}
}
}
}
}
}
`;
Notice what's happening here: GraphQL lets you request exactly the fields you need. No over-fetching. This is one of the core reasons headless Shopify outperforms Liquid, you're not pulling down a bloated HTML page with dozens of fields you'll never render. You're pulling down precisely structured data, and nothing else.
Shopify handles your commerce data. BILDIT handles everything else: the marketing content, the editorial pages, the promotional banners, and the personalized experiences that turn a product listing into a conversion engine.
BILDIT's Commerce Suite is designed to work alongside platforms like Shopify Plus without replacing them. It connects via API, and your Next.js app pulls content the same way it pulls product data, through a typed fetch layer.
// lib/bildit.ts
const BILDIT_API_URL = process.env.BILDIT_API_URL!;
const BILDIT_API_KEY = process.env.BILDIT_API_KEY!;
export async function bildtFetch<T>(endpoint: string, params?: Record<string, string>): Promise<T> {
const url = new URL(`${BILDIT_API_URL}/${endpoint}`);
if (params) {
Object.entries(params).forEach(([key, value]) => url.searchParams.set(key, value));
}
const response = await fetch(url.toString(), {
headers: {
'Authorization': `Bearer ${BILDIT_API_KEY}`,
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`BILDIT API error: ${response.status}`);
}
return response.json() as Promise<T>;
}
With this in place, you can fetch landing page content, promotional banners, and editorial blocks from BILDIT and render them alongside Shopify product data in a single page component. The key insight is that content and commerce are separate concerns and keeping them separate is what gives you the flexibility to update marketing copy without touching product logic, and vice versa.
This separation also matters for performance. Marketing pages that rarely change can be fully statically generated. Product pages that update with inventory and pricing can use Incremental Static Regeneration (ISR). Each content type gets the rendering strategy it deserves.
Now the architecture pays off. You have two data sources, Shopify for commerce, BILDIT for content, and Next.js to stitch them together.
Product pages are the core of any eCommerce site, and they are where ISR shines. The idea is simple: pre-render the page at build time for maximum speed, then silently regenerate it in the background when the data changes, without rebuilding the entire site.
// app/products/[handle]/page.tsx
import { shopifyFetch } from '@/lib/shopify';
import { GET_PRODUCT_BY_HANDLE } from '@/lib/shopify/queries';
import Image from 'next/image';
// Revalidate every 60 seconds — price and inventory updates
// are reflected within a minute, without a full rebuild
export const revalidate = 60;
export default async function ProductPage({ params }: { params: { handle: string } }) {
const data = await shopifyFetch<ProductResponse>(GET_PRODUCT_BY_HANDLE, {
handle: params.handle,
});
const product = data.product;
return (
<div className="max-w-4xl mx-auto p-6">
<Image
src={product.featuredImage.url}
alt={product.featuredImage.altText}
width={product.featuredImage.width}
height={product.featuredImage.height}
priority // Above-the-fold hero image — load it immediately
sizes="(max-width: 768px) 100vw, 50vw"
/>
<h1 className="text-3xl font-bold mt-4">{product.title}</h1>
<p className="text-xl text-gray-700 mt-2">
${product.priceRange.minVariantPrice.amount}
</p>
<div dangerouslySetInnerHTML={{ __html: product.descriptionHtml }} />
{/* Variant selector and add-to-cart logic here */}
</div>
);
}
Two things to notice. First, revalidate = 60 means Next.js will serve the statically generated version of this page for up to 60 seconds, then silently regenerate it in the background on the next request. Users always get a fast static page. Price changes are reflected within a minute. No full site rebuild needed.
Second, the <Image /> component with priority on the hero image. This tells Next.js to preload this image immediately, it's the Largest Contentful Paint element on the page, and getting it on screen fast is the single most impactful thing you can do for your LCP score.
For landing pages, campaign pages, and editorial content, BILDIT delivers structured content blocks that your React components render:
// app/campaigns/[slug]/page.tsx
import { bildtFetch } from '@/lib/bildit';
export const revalidate = 300; // Marketing pages change less frequently
export default async function CampaignPage({ params }: { params: { slug: string } }) {
const page = await bildtFetch<CampaignPage>('pages', { slug: params.slug });
return (
<div>
{page.blocks.map((block) => (
<ContentBlock key={block.id} type={block.type} data={block.data} />
))}
</div>
);
}
This is where the mobile commerce shift becomes tangible. Marketing pages rendered this way, statically generated, image-optimized, layout-stable, perform dramatically better on mobile than anything a Liquid template can produce. And mobile is where the majority of eCommerce traffic now originates.
The cart is the most critical piece of state in any eCommerce application, and in a headless setup, it lives in Shopify, not in your frontend. Your Next.js app holds a reference to the cart ID and talks to Shopify's Cart API via GraphQL mutations.
// lib/shopify/cart.ts
import { shopifyFetch } from '@/lib/shopify';
export async function createCart() {
const mutation = `
mutation CreateCart {
cartCreate(input: {}) {
cart { id }
}
}
`;
const data = await shopifyFetch<{ cartCreate: { cart: { id: string } } }>(mutation);
return data.cartCreate.cart.id;
}
export async function addToCart(cartId: string, variantId: string, quantity: number) {
const mutation = `
mutation AddToCart($cartId: ID!, $lines: [CartLineInput!]!) {
cartLinesAdd(cartId: $cartId, lines: $lines) {
cart {
lines(first: 100) {
edges {
node {
id
quantity
merchandise {
... on Product { title }
}
}
}
}
}
}
}
`;
return shopifyFetch(mutation, {
cartId,
lines: [{ merchandiseId: variantId, quantity }],
});
}
Store the cart ID in a cookie (not localStorage, cookies work on the server side too, which matters for SSR). When the user is ready to checkout, redirect them to Shopify's hosted checkout page via the cart's checkoutUrl. This is the pragmatic choice: Shopify's checkout handles PCI compliance, fraud detection, Shop Pay, and payment processing. Rebuilding that from scratch would be insane.
This is also where the checkout experience becomes a conversion lever. A fast, friction-free path from "add to cart" to "order confirmed", with Shop Pay's one-click option, is one of the highest-ROI optimizations available to any eCommerce operator.
This is the part that makes the business case undeniable. Let's look at what actually changes when you make this migration.
Here's a representative comparison between a well-optimized Shopify Liquid theme and the same storefront rebuilt as a headless Next.js application, tested on identical product pages:
Metric
Shopify Liquid Theme
Headless Next.js
Performance
38–52
88–96
LCP
3.8–5.2s
0.9–1.8s
CLS
0.18–0.34
0.02–0.06
INP
280–420ms
60–140ms
Accessibility
72–81
90–97
SEO
78–85
92–98
These aren't cherry-picked numbers. They reflect consistent patterns across multiple headless migrations. The performance gap is driven by three structural advantages: static HTML served from CDN edges (eliminating server-side Liquid rendering on every request), Next.js's automatic image optimization (format conversion, responsive srcsets, lazy loading), and the elimination of render-blocking third-party scripts that accumulate in Liquid themes over time.
Lighthouse scores are synthetic benchmarks. What matters more is what happens with actual users. The data here aligns with broader industry findings:
Vodafone reported that improving LCP by 31% led to an 8% increase in sales and a 15% improvement in cart-to-visit rate. Renault saw a 1-second LCP improvement translate to a 14% reduction in bounce rates and a 13% conversion lift. Each 100ms increase in LCP correlates with a 1–3% decrease in conversion rates across eCommerce benchmarks (Google CrUX analysis, 2024–2025).
For a headless Next.js storefront achieving LCP under 1.5 seconds on mobile — which is well within reach with proper ISR and image optimization — the conversion implications are significant. You're not just faster. You're faster in exactly the ways Google's ranking algorithms reward, and in exactly the ways that keep shoppers on your site long enough to buy.
Two areas where composable architecture delivers outsized returns: images and personalization.
Images are often one of the biggest contributors to page weight on commerce pages, which is why responsive image sizing, modern formats, and careful loading behavior matter so much.
· Format conversion: Serves WebP or AVIF to browsers that support them, falling back to JPEG/PNG for others, without you writing a single line of format-detection code.
· Responsive sizing: The sizes prop tells the browser which image dimensions to request based on viewport width. Next.js generates the srcset automatically.
· Lazy loading: Images below the fold load only when they scroll into view. The priority prop overrides this for above-the-fold elements like hero images.
· Placeholder blur: A low-resolution placeholder renders instantly while the full image loads, eliminating the jarring "content flash" that drives CLS scores up.
<Image
src={product.featuredImage.url}
alt={product.featuredImage.altText}
width={800}
height={800}
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
placeholder="blur"
blurDataURL={product.featuredImage.blurPlaceholder}
loading="lazy" // Default for non-priority images
/>
This single component, applied consistently across your product catalog, can cut image-related page weight by 40–60% compared to unoptimized Liquid themes.
Personalization in a composable architecture isn't an afterthought, it's a structural advantage. Because your content layer (BILDIT) is decoupled from your commerce layer (Shopify), you can serve different content to different users without rebuilding pages.
BILDIT's AI-driven personalization capabilities allow you to customize the content experience based on user behavior, purchase history, and even the emerging signals from AI search engines. As the commerce landscape shifts toward agentic discovery (where AI systems shop on behalf of consumers) having clean, structured, machine-readable product data becomes as important as having fast page loads.
The practical implementation looks like this: your Next.js app fetches a base page from BILDIT, then enriches it with personalized content blocks based on a user identifier (stored in a cookie or session). The personalized blocks are fetched client-side, after the static shell has already rendered, so they don't impact LCP or initial page load time.
This is the same pattern that native mobile apps use to drive higher conversion rates than web-based alternatives. The key insight: personalization should enhance the experience after the page is already fast, not slow it down in pursuit of relevance.
Headless is not a free lunch. The teams that succeed with this architecture are the ones who go in with their eyes open.
Development complexity is real. You're now responsible for SEO (sitemaps, meta tags, structured data), analytics instrumentation, and every piece of frontend infrastructure that Shopify's theme engine used to handle automatically. A senior Next.js developer is not optional, it's the minimum viable team for a production headless storefront.
The cost structure shifts. You're trading Shopify's theme hosting for Vercel (or equivalent), adding a license for BILDIT, and potentially increasing developer time. For stores under $10 million in annual revenue, the ROI math is tighter. For stores above that threshold — particularly those with significant mobile traffic — the performance and conversion gains typically justify the investment within six to twelve months.
Checkout remains Shopify's. In this architecture, you're redirecting to Shopify's hosted checkout at the end of the cart flow. It's the pragmatic choice for security and compliance, but it means you can't fully control the end-to-end visual experience. Shopify Plus does offer Shopify Functions and checkout extensibility that mitigate this, but it's still a constraint worth acknowledging.
SEO requires intentional work. A Liquid theme ships with Shopify's built-in SEO features, auto-generated sitemaps, basic meta tags. In a headless setup, you build these yourself. Next.js makes it straightforward with its <Head> component and dynamic metadata generation, but it's work that needs to be planned for, not discovered after launch.
What is a headless Shopify Plus store? A headless Shopify Plus store separates the frontend storefront from Shopify's commerce backend. Shopify continues to manage products, inventory, orders, and payments. A custom frontend, built with Next.js or another framework, connects to Shopify via the Storefront GraphQL API and renders the shopping experience independently.
How do Core Web Vitals affect eCommerce conversion rates? Better speed and visual stability generally reduce friction, which gives merchandising and conversion work a stronger foundation.
Why use Next.js for a headless Shopify storefront? Next.js offers Incremental Static Regeneration (ISR), automatic image optimization, server components, and edge-native deployment on Vercel. These features map directly to the performance requirements of eCommerce: fast initial loads, fresh product data, optimized images, and global CDN delivery. It's the most battle-tested React framework for this use case.
What is Incremental Static Regeneration and why does it matter for eCommerce? ISR lets Next.js pre-render pages as static HTML at build time, then silently regenerate them in the background when data changes without rebuilding the entire site. For eCommerce, this means product pages load instantly from CDN cache, while price and inventory updates are reflected within seconds or minutes. It's the sweet spot between static performance and dynamic data freshness.
How does BILDIT integrate with a Shopify Plus headless setup? BILDIT connects to your Next.js frontend via API, delivering marketing content (landing pages, banners, promotional copy) as structured data. Your React components render this content alongside Shopify product data. BILDIT's Shopify Plus integration keeps commerce and content in sync without custom middleware.
What happens to checkout in a headless Shopify store? In most composable architectures, checkout redirects to Shopify's hosted checkout page. This handles PCI compliance, payment processing, fraud detection, and Shop Pay integration. Shopify Plus merchants can extend checkout behavior using Shopify Functions, but the core checkout flow remains Shopify-managed.
How long does a headless Shopify Plus migration take? A well-scoped migration with an experienced Next.js team typically takes 6–12 weeks for a mid-complexity storefront. The timeline depends on the number of page types, the complexity of your product catalog, the depth of third-party integrations, and how much custom functionality your current Liquid theme has accumulated.
Is headless worth it for smaller Shopify Plus stores? It depends on your traffic composition and growth trajectory. Stores with heavy mobile traffic, high-value products, or aggressive conversion targets tend to see ROI faster. Stores under $5 million in revenue may find that optimizing their existing Liquid theme delivers better near-term returns for the investment required.
The composable commerce architecture described in this guide isn't experimental anymore. It's the direction the industry is moving, and the tools have matured enough that mid-market merchants can execute it without an enterprise-scale engineering team.
Two trends are worth watching closely. The first is the rise of AI-driven product discovery, where LLMs and AI search engines surface products based on conversational queries rather than keyword searches. Brands with clean, structured, API-first data architectures are positioned to benefit from this shift. Brands locked into monolithic storefronts are not.
The second is the continued evolution of edge computing and serverless infrastructure. Vercel's edge network, Cloudflare, and Netlify are all pushing compute closer to the user and reducing latency at the infrastructure level. A Next.js app deployed to these platforms benefits from this automatically. A Liquid theme does not.
The migration from a traditional Shopify theme to a headless Next.js storefront is, at its core, a bet on performance as a competitive advantage. The data supports that bet. The tools make it achievable. And the stores that make this move now, before it becomes table stakes, will find themselves with a structural edge that compounds over time.
The architecture is sound. The path is clear. The only question is when you start.
· Google Search Core Web Vitals: https://developers.google.com/search/docs/appearance/core-web-vitals
· PageSpeed Insights and Core Web Vitals overview: https://developers.google.com/speed/docs/insights/v5/about
· Shopify headless commerce overview: https://www.shopify.com/plus/solutions/headless-commerce
· Next.js Production Checklist: https://nextjs.org/docs/pages/guides/production-checklist
· BILDIT homepage / Visual Experience Engine: https://bildit.co/