Building a Scalable Headless Shopify Storefront with Next.js

Sep 23, 2025

A headless Shopify approach decouples your storefront UI from Shopify’s backend. Instead of using Liquid themes on Shopify’s servers, you’ll build a custom Next.js frontend that communicates with Shopify via APIs. The key enabler is Shopify’s Storefront GraphQL API, which provides access to products, collections, carts, customer data, etc., for any external application . This API-driven architecture offers full freedom to craft a unique UI/UX and use modern web tools, while Shopify continues to handle core e-commerce functions (inventory, orders, payments).

Shopify Hydrogen is an alternative official solution: it’s a React-based framework specifically for Shopify headless stores. Hydrogen comes with pre-built components, hooks, and utilities (for cart, Shopify analytics, Shop Pay integration, etc.) tightly integrated with Shopify’s ecosystem . It utilizes React Server Components and edge rendering on Shopify’s Oxygen hosting for performance. However, Hydrogen is locked into React and Shopify’s tooling, whereas using the Storefront API directly allows you to choose Next.js (or any framework) and host anywhere . In practice, Next.js is a popular choice due to its powerful rendering techniques (SSR, SSG, ISR) and huge ecosystem. Hydrogen provides convenience with Shopify-specific modules, but Next.js gives more flexibility and a larger community support base. (Notably, Shopify now even offers a “Hydrogen React” component library that can be used within any React app, so you could leverage Shopify’s pre-built React components inside a Next.js project if desired .)

Recommendation: For a production, high-revenue store, Next.js with the Storefront API is a proven stack. It lets you build a fully custom storefront while still leveraging Shopify’s reliable backend. You’ll have to implement more of the storefront logic yourself (compared to using Hydrogen’s out-of-the-box components), but the payoff is greater control over technology and hosting. Next, we’ll outline how to set up and build this headless solution.

Setting Up Shopify Storefront API Access

To get started, you need to create API credentials for Shopify and a Next.js app:

  • Shopify Storefront API Credentials: In your Shopify admin, create a new private (custom) app or use the “Headless channel” to get Storefront API access. Enable the required Storefront API scopes (e.g. read products, customer, cart, etc.) and then install the app to generate a Storefront API Access Token  . Copy the storefront access token and note your store’s GraphQL endpoint URL (usually https://<your-store>.myshopify.com/api/<version>/graphql.json) .

  • Next.js Project Initialization: Spin up a Next.js project (e.g. with npx create-next-app) and add the Shopify credentials to your environment. For example, in .env.local set:

SHOPIFY_STORE_DOMAIN="<your-store>.myshopify.com"  
SHOPIFY_STOREFRONT_ACCESS_TOKEN="<your-access-token>"
  • Your Next.js app will use these to authenticate requests to Shopify . Never expose the token in client-side code – keep it in environment variables and use Next.js server-side functions or API routes to interact with Shopify.

  • Data Fetching Setup: Next.js can communicate with Shopify’s GraphQL API using fetch or any GraphQL client. For example, you can create a helper function to call the GraphQL endpoint with the proper headers: include the Shopify store URL as the fetch URL and set the X-Shopify-Storefront-Access-Token header to your token . This token identifies your store and has the permissions you configured. Using GraphQL allows you to request exactly the data you need (products, collections, etc.), minimizing over-fetching and improving performance .

With the API connection in place, you’re ready to query product data and build out the frontend.

Querying Product & Collection Data (GraphQL)

Using the Storefront API’s GraphQL, you can retrieve all the data needed for your Next.js pages. Shopify provides a comprehensive schema for products, collections, variants, images, etc. Here’s how to use it in practice:

  • GraphQL Queries: Construct queries to get the necessary fields. For example, you might fetch a list of products with their titles, images, and prices to display on a landing page. In Next.js, you can do this in getStaticProps or getServerSideProps for a page. For instance, a query to fetch all products could look like:

query GetAllProducts {
  products(first: 100, sortKey: TITLE) {
    edges {
      node {
        id
        title
        handle
        description
        priceRange {
          minVariantPrice { amount currencyCode }
        }
        images(first:1) { edges { node { src altText } } }
      }
    }
  }
}


  • This would return a list of products with basic info. You can adjust fields and filters as needed. Using GraphQL means you only receive the fields you ask for, which is efficient for performance .

  • Authenticating Requests: Every request to the Storefront API must include the store’s GraphQL endpoint and the access token header. For example, using fetch:

const res = await fetch(`https://${process.env.SHOPIFY_STORE_DOMAIN}/api/2023-04/graphql.json`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Shopify-Storefront-Access-Token': process.env.SHOPIFY_STOREFRONT_ACCESS_TOKEN
  },
  body: JSON.stringify({ query, variables })
});
const responseData = await res.json();


  • This will return responseData.data with your requested fields. You can wrap this in a helper function (as shown in Vercel’s guide) to reuse for different queries  . Popular libraries like graphql-request or Apollo Client can also be used to simplify GraphQL calls.

  • Next.js Data Fetching: For a high-traffic store, prefer static generation where possible. Product listing pages and individual product pages can often be generated at build time (or incrementally) for speed. Use getStaticPaths and getStaticProps for product pages if the catalog is not too large, or use getServerSideProps for server-rendering on demand (useful if inventory or pricing updates frequently). The goal is to have pages pre-rendered as HTML with product data for SEO and performance. You can also implement Incremental Static Regeneration (ISR) to periodically revalidate pages and fetch updated data without a full rebuild.

  • Example – Product Page: A Next.js page at /products/[handle].js could use getStaticPaths to pre-generate pages for a subset of popular products (or all, if feasible) and getStaticProps(context) to fetch a single product by its handle or ID via a GraphQL query (productByHandle or node(id: ...)). This returns product details which you pass into the React component to render. By doing this server-side, each product page is delivered with full HTML content (title, description, etc.), benefiting SEO.

In summary, configure your Next.js app to securely call Shopify’s Storefront API and fetch product data server-side. This setup forms the foundation for building out features like the shopping cart and checkout, described next.

Cart Management and Persistent Cart State

Handling the shopping cart in a headless scenario is a crucial part of the build. Shopify’s Storefront API provides a Cart API (GraphQL mutations and queries) to mimic Shopify’s native cart behavior on your Next.js site. The major tasks are: creating a cart, adding/removing items, updating quantities, and maintaining the cart state across pages or sessions. Here’s how to implement it:

  • Creating a New Cart: When a customer clicks “Add to Cart” for the first time, you’ll want to create a cart object on Shopify. Use the cartCreate GraphQL mutation, passing the chosen variant ID (merchandiseId) and quantity. This will return a new cart ID (globally unique identifier) and the initial cart contents. For example, cartCreate mutation can create a cart with a line item in one step  . The cart ID is important – it’s how you reference and modify this cart in subsequent calls.

  • Adding Items to Cart (Existing Cart): If the user already has a cart (you have a cart ID stored), you can add more items to it with cartLinesAdd (or update existing lines with cartLinesUpdate, remove with cartLinesRemove). These mutations require the cart ID and the new line items (variant ID and quantity)  . In practice, your front-end code should check if a cart ID exists in storage (meaning the user has an open cart). If no cart exists, create one; if it exists, add to that cart. For example, one approach is:

let cartId = sessionStorage.getItem("cartId");
if (!cartId) {
  // No cart yet: create a new cart with this item
  const result = await shopifyGraphQL(cartCreateMutation, { variantId, quantity });
  cartId = result.data.cartCreate.cart.id;
  sessionStorage.setItem("cartId", cartId);
} else {
  // Existing cart: add the item to it
  await shopifyGraphQL(cartLinesAddMutation, { cartId, variantId, quantity });
}


  • This logic ensures the user’s cart is preserved. (You could also use localStorage for persistence beyond a session, or cookies. In a production app, you might store the cart ID in a HTTP-only cookie and perform cart mutations via Next.js API routes for security.)

  • Persistent Cart State: To keep the cart alive between page navigations or visits, store the cartId on the client side. Many implementations use localStorage or sessionStorage to save the cart ID after creation  . This way, if the user reloads the site, your code can retrieve the cart ID and query the cart’s contents from Shopify. The Storefront API allows you to fetch a cart by ID (query { cart(id: $cartId) { ...lines, cost, etc. } }). By querying the cart, you can display the current items and totals in a mini-cart or cart page at any time. (Do note that the cart ID contains a “secret” token; avoid leaking it. Keep it client-side or in a cookie – never put it in a URL or expose it to other users .)

  • Updating and Displaying Cart Contents: With the cart ID, you can poll or fetch the latest cart state. For example, after any mutation (add/remove), you might re-query the cart to get updated line items and subtotal. The Storefront API’s cart object can return each line’s product variant details (title, price, image) and the aggregated cost breakdown  . Use this data to render the cart UI in Next.js. Since the Next app controls the front-end, you have full freedom to design the cart drawer or page (and you can reuse Shopify’s logic such as disabling checkout if cart is empty, etc.).

  • Edge Cases: Make sure to handle edge cases like out-of-stock products or quantity limits – the API will return errors if, for instance, you try to add more items than available. Also, consider cart expiration: Shopify carts don’t truly “expire” quickly, but it’s good practice to occasionally refresh or re-create a cart if it’s very old to ensure pricing is up to date. For logged-in customers, you can also associate the cart with a customer ID (using buyerIdentity on the cart) so that if they log in on another device, you could theoretically restore their cart. However, managing cross-device carts may require additional logic (and the Customer API).

In summary, use Shopify’s Cart GraphQL API to maintain cart state. The Next.js frontend stores the cart ID and uses mutations to add/update items. This offloads heavy lifting (tax calculations, discounts, etc.) to Shopify while you manage the UI. With a persistent cart ID, customers can continue shopping seamlessly across pages.

Checkout Integration (Shopify-Hosted Checkout)

One huge advantage of sticking with Shopify for the backend is leveraging Shopify’s secure and optimized checkout process. You do not need to rebuild the entire checkout in Next.js – instead, your custom site will hand off to Shopify’s hosted checkout when the user is ready to pay. The typical flow is:

  1. Customer Clicks “Checkout”: In your Next.js cart page or drawer, the user will hit a Checkout button.

  2. Generate Checkout URL via Storefront API: You will use Shopify’s API to obtain a checkout URL for the current cart. The Storefront API exposes a checkoutUrl field on the Cart object. By querying the cart with its ID, you can retrieve a URL that corresponds to that cart’s checkout in Shopify . For example, a GraphQL query:

query getCheckout($cartId: ID!) {
  cart(id: $cartId) {
    checkoutUrl
  }
}


  1. will return a URL like https://your-store.myshopify.com/cart/c/<token> which is the web checkout link for that cart . This URL already includes the cart’s line items and will take the user to the first step of checkout (contact/shipping info).

  2. Redirect the User to Shopify Checkout: Once you have the checkoutUrl, simply redirect the user’s browser to that URL (e.g., window.location.href = checkoutUrl). This transports the user into Shopify’s domain to complete payment. All the cart items are preserved and shown on the Shopify checkout pages. Because this is Shopify’s standard checkout, it supports all the payment methods (including Shopify Payments, PayPal, etc.), discount codes, shipping calculation, and so on, exactly as a normal Shopify store would. It’s fully secure and PCI compliant, which means you don’t have to worry about handling sensitive payment data on your Next.js server.

  3. Order Completion: After the user pays and completes the checkout, they’ll see the usual Shopify order confirmation. You can configure the branding on the checkout (logo, colors) in Shopify admin so it looks consistent with your Next.js storefront. Typically, you might also set up a redirect back to a custom order confirmation page on your site via the thank-you page script, but that can be optional. The order will appear in Shopify admin as usual, attributed to the “Headless” channel (so you know it came from your custom storefront).

Shopify recommends fetching a fresh checkout URL just-in-time when the user is ready to checkout (and not reusing an old one), to ensure pricing, availability, and customer identity are all up to date . In practice, the checkoutUrl is valid for a short time and will reflect any latest cart updates.

Note: The checkout will occur on Shopify’s domain (or your myshopify.com subdomain, or a Shopify Plus custom domain if applicable). This is normal – it’s the trade-off for using Shopify’s secure checkout. Make sure to inform users (e.g., via the UI) that they’ll be redirected to a secure payment page. Once the order is done, you can bring them back to a Next.js page (for example, by providing a “Continue Shopping” link or automating a redirect post-purchase).

By using Shopify’s hosted checkout, you offload all payment security and complexity to Shopify, which is a huge win. All your Next.js app needs to do is create carts and send customers to the checkout URL. Shopify will handle payment processing, fraud checks, sending confirmation emails, etc. – just as it would for a normal Shopify theme store.

SEO and Performance Considerations

One of the motivations for going headless with Next.js is to improve site performance and SEO beyond what a Liquid theme can offer. Here are best practices to ensure your custom storefront is fast and search-engine friendly:

  • Server-Side Rendering (SSR) and Static Generation: Next.js supports SSR and SSG, which are critical for SEO. By rendering product and collection pages on the server (or at build time), you ensure that search engine crawlers see fully populated HTML content (product names, descriptions, metadata) without relying on client-side JavaScript. This greatly improves crawlability and indexing. In fact, using SSR/SSG means your pages can be as SEO-friendly as a traditional Shopify theme, if not more. Dynamic meta tags and <head> content can be set for each page using Next’s <Head> component or the newer App Router metadata APIs. Be sure to include unique titles and meta descriptions for products and categories – you can fetch these via the Storefront API and inject them server-side  . Also consider generating an XML sitemap of your pages (Next.js can output this via an API route or build script) to help search engines discover all product URLs.

  • Performance Optimizations: Next.js gives you fine-grained control to build a high-performance site:


    • Use Static Site Generation for as many pages as possible (with incremental regeneration) to serve pre-computed pages via CDN, resulting in near-instant page loads.

    • Leverage code-splitting – Next.js automatically splits your JavaScript by page, so users only download the code needed for the page they’re on .

    • Implement lazy loading for below-the-fold images and dynamic imports for heavy components to reduce initial load time .

    • Use Next.js Image Optimization for product images. You can pipe Shopify image URLs into Next’s <Image> component, which will auto-generate optimized, responsive images and serve them from a CDN.

    • Enable Caching: Cache API responses from Shopify when possible. For example, product data that doesn’t change often can be cached in memory or revalidated periodically to avoid hitting rate limits. Next.js ISR is a great tool here – you can revalidate a page every X minutes to fetch fresh inventory data in the background, ensuring users see up-to-date info without a performance hit.

    • Monitor your performance (use tools like Google Lighthouse or Vercel Analytics). A fast site not only improves user experience but also positively impacts SEO ranking.


  • SEO Best Practices: In a headless build, you must implement some things manually that Shopify themes did for you:


    • Ensure each page has correct canonical URLs, meta tags, and structured data. For example, you might add JSON-LD structured data for products (price, availability, ratings) in the HTML so Google can generate rich snippets.

    • Handle 404 pages and redirects (especially if your URL structure changes from the original store).

    • If you move to a new domain with this Next.js site, set up proper 301 redirects from the old Shopify URLs to new ones to preserve SEO equity.

    • Use the Storefront API to fetch things like product SEO descriptions or titles if you maintained them in Shopify, or manage them in a CMS.

    • Consider a headless CMS for content pages/blogs or use Shopify’s built-in blog via API. Many headless stores integrate a CMS (Contentful, Sanity, etc.) for managing non-product content  . This can help marketers easily edit landing pages and improve SEO with fresh content.

In short, Next.js can deliver an extremely fast experience (often faster than Shopify’s native frontend) if you utilize SSR/SSG and modern web performance techniques. Shopify’s backend will scale to handle data and traffic, while your Next.js frontend, especially when deployed on a platform like Vercel, will serve pages quickly around the globe.

Deployment and Hosting (e.g. Vercel)

You’ll need to host your Next.js application on an infrastructure that supports server-side rendering and static asset delivery. Vercel is a popular choice (it’s the company behind Next.js), offering an optimized experience for Next.js apps:

  • Easy Deployment: You can connect your Git repository to Vercel and it will automatically build and deploy your Next.js app on every push. Vercel auto-detects Next.js and applies the correct build settings . It also lets you define your environment variables (like the Shopify token and domain) securely in the project settings . This means no secret is exposed in the client bundle – Vercel provides those vars to serverless functions at runtime.

  • Global CDN and Edge Network: Vercel will distribute your static assets (pre-rendered pages, images, JS chunks) on a global CDN. Furthermore, Next.js dynamic SSR routes will be served from Vercel’s edge locations if you enable their Edge Functions or use Middleware. This ensures low latency response to users around the world. For a high-revenue store, this global performance is a big plus. Essentially, Vercel gives you Shopify-like CDN coverage for your custom frontend out of the box.

  • Scalability: Vercel (and similar hosts like Netlify) can scale to handle large traffic spikes by instantiating more serverless functions as needed. Because your backend remains Shopify, the heavy lifting of order processing is on Shopify’s side, and your Next.js app mainly needs to scale for read traffic (product pages, etc.) and some write ops (cart mutations). Ensure your Shopify plan’s Storefront API call limits can handle peak loads – if not, implement caching on your side. Vercel’s serverless model means you don’t manage servers, and you pay for usage, which generally scales well for high traffic events (you might consider Vercel Pro or Enterprise for very large scales).

  • Alternate Hosting: If not using Vercel, you could host on AWS (Elastic Container or CloudFront + Lambda), Google Cloud, or even on Shopify’s Oxygen (if using Hydrogen, but with Next.js you’d stick to a third-party host). Another viable host is Netlify, which also supports Next.js builds (including SSR via Netlify Functions). The choice comes down to preference; Vercel is known for convenience with Next.js. In any case, use a custom domain for your storefront (e.g. store.yourdomain.com or yourdomain.com pointed at your Next.js app) and update your Shopify settings to use that domain for the headless store channel, so that everything is under a consistent URL for users.

  • Monitoring and Logging: In production, set up monitoring for both your Next.js app (Vercel provides logs and analytics) and your Shopify API usage. Shopify may rate-limit if you exceed API call thresholds. For a largely static site with ephemeral cart calls, this is rarely an issue, but keep an eye on it. Also, handle errors gracefully – e.g., if the Storefront API is down or returns an error, your Next.js pages should fail gracefully (perhaps show an error message or fallback). These scenarios are rare, but a plan should exist since your revenue depends on uptime.

By hosting on a platform like Vercel, you gain deployment simplicity and performance. After deploying, test your site thoroughly: check that all pages load correctly via the CDN, the cart works end-to-end (including redirect to checkout), and that SEO meta tags are present in page source. Once everything checks out, you can flip the switch to direct your production traffic to the new Next.js storefront.

Limitations and Risks of a Fully Headless Setup

Moving to a fully custom headless architecture gives great power, but it also comes with trade-offs. Be aware of these limitations and challenges:

  • Increased Complexity: A headless store is more technically complex than a theme-based Shopify store. You are effectively running two systems (Shopify backend + custom frontend). Implementing this “from scratch” is time-consuming and requires solid development expertise  . There’s no Shopify Drag-and-Drop Editor or pre-built theme to fall back on – any UI changes or new features will require code deployments. Plan for a proper development team to build and maintain the storefront.

  • Losing Some Out-of-the-Box Features: Shopify themes come with a lot of built-in functionality (e.g. sections, theme settings, quick integrations for things like currency converters, etc.). In headless, you must recreate or obtain equivalents for these. For example, if your current store uses a Shopify app for product reviews that injects Liquid code, that won’t automatically work on a Next.js site. You’d need to use the app’s API or a widget version of it, or switch to a SaaS that supports headless. Many Shopify apps are built for the theme ecosystem and might not work by default on a headless site  . You may have to find workarounds or headless-friendly apps. This includes things like wishlist, reviews, upsell popups, etc. – check with your app vendors if they support headless usage.

  • Maintenance and Updates: Both Shopify and Next.js will continue to evolve. You need to keep your Storefront API version up to date (Shopify releases API versions quarterly). You also need to maintain the Next.js app (applying security updates, upgrading Next.js versions, etc.). When Shopify releases new features (e.g. a new checkout extensibility feature or a new field on products), you won’t automatically get them – you’d have to update your frontend to utilize them. Ensure you have a maintenance plan.

  • Performance Pitfalls: While headless can be faster, it’s not guaranteed unless you implement it right. A poorly built headless site (e.g. one that fetches lots of data client-side or blocks rendering unnecessarily) could actually be slower. Using Next.js best practices (SSR/SSG, minimal JS, efficient data fetching) is key. Also, consider the cost of additional round trips – e.g., loading the cart or certain dynamic sections might require extra API calls. Mitigate this with caching or batch queries when possible. The good news is the Storefront API is quite fast, and GraphQL lets you get many details in one request, but be mindful of not over-querying (there are rate limits, roughly 1,000 points per minute baseline; a single complex query might cost 10-50 points). Use Shopify’s recommended practices to stay within limits (like querying only what you need, and using persisted queries or conditional requests if applicable).

  • SEO and Analytics Considerations: As noted, you must handle SEO on your own. This also extends to analytics and tracking. In a Liquid theme, Shopify and various apps might automatically include certain script tags (for Google Analytics, Facebook Pixel, etc.). In your Next.js app, you will need to add these tracking scripts manually (or via tools like Google Tag Manager). Make sure to replicate essential tracking so that you don’t lose marketing data when migrating to headless. Also, check that things like UTM parameters are preserved if you redirect through to checkout (Shopify will carry them over to the order as long as they’re in the checkout URL).

  • Content Management: If your team (merchandisers, marketers) is used to using Shopify’s theme editor or page builder apps to create content, they will face a learning curve. In headless, content changes often require developer input or a separate CMS. You might integrate a headless CMS to empower non-developers to change certain content without code. This is an extra component in your stack to manage, but it can be worthwhile for flexibility.

  • Cost and Effort: Building a headless storefront is typically a significant upfront investment. It’s often justified for large or fast-growing brands where the flexibility and performance gains drive more revenue to outweigh the cost. However, smaller businesses might find the ROI isn’t there. Since your store is already “significant revenue,” you likely have justification, but ensure that going headless aligns with clear business goals (faster site = better conversion, custom UX = brand differentiation, etc.). Also be prepared for ongoing costs (hosting fees on Vercel, developer retainer for maintenance, etc.) which are now separate from Shopify’s fees. On the plus side, you are not paying Shopify for Plus just to get customization – even a Shopify Advanced plan can do headless, as all plans have API access  .

Despite these challenges, many brands successfully run headless Shopify stores (examples include companies using Next.js Commerce or custom builds). The key is to plan and architect carefully. Start with essential features (product display, cart, checkout integration), then iteratively bring in enhancements (CMS, personalization, etc.). Keep Shopify’s best practices in mind and leverage the community – since headless commerce is popular, there are plenty of resources and libraries to assist (for example, the Next.js Commerce starter or Shopify’s Hydrogen React components).

Conclusion and Recommendations

Building a fully custom Next.js storefront on Shopify’s backend can deliver a high-performance, tailored shopping experience – as long as it’s done with production-grade practices. In summary:

  • Use the Shopify Storefront API as the backbone for all commerce data and actions, and keep your integration up to date with Shopify’s latest improvements. It provides the power to retrieve products, collections, and manage carts and checkouts headlessly  .

  • Leverage Next.js features (SSR, SSG, ISR) to ensure SEO and performance are first-class. Pre-render as much as possible, use global CDN hosting (like Vercel) for speed, and take advantage of Next.js optimizations. A fast site will improve user experience and can lead to higher conversion rates  .

  • Implement robust cart and checkout flows using Shopify’s APIs rather than attempting to roll your own. This keeps sensitive operations secure and utilizes Shopify’s proven checkout system. Always fetch a fresh checkoutUrl when the user is ready to pay, and let Shopify handle the heavy lifting of payments .

  • Plan for the long term: put in place analytics, error monitoring, and a maintenance schedule for your headless store. Ensure your team (or agency) is ready to support additional development needs, whether it’s integrating a third-party review system via API or adjusting to Shopify API changes.

By following these guidelines and best practices from Shopify’s documentation and real-world developer experiences, you can build a scalable, resilient headless storefront. The result will be a Next.js front-end that delivers a modern app-like shopping experience, powered by the reliability and scalability of Shopify on the back-end. This architecture is increasingly common for high-volume e-commerce sites in 2025, and with careful execution, it can unlock better site performance, custom branding, and innovation that ultimately drive more growth for your store. 

Share This Article

Let's talk shop

Karl Johans gate 25. Oslo Norway

Make it happen

Let's talk shop

Karl Johans gate 25. Oslo Norway

Make it happen

Let's talk shop

Karl Johans gate 25. Oslo Norway

Make it happen