Skip to content

Selling Products with Shopify

Shopify's Storefront API exposes your entire product catalog through GraphQL which makes it easy to fetch your products at build time, generate your store pages statically, and let Shopify host the checkout. No backend required.

Manage your products, inventory, and pricing entirely in Shopify. Every time your site builds, ZeroPoint fetches the latest data and regenerates your store. Customers check out on Shopify's fully hosted, PCI-compliant checkout — no payment data touches your ZeroPoint site.

How it works

  1. Create products in your Shopify store
  2. At build time, ZeroPoint fetches your product catalog from the Storefront API
  3. Your store pages are generated as static HTML — no runtime server needed
  4. Customers click "Buy Now" and are taken directly to Shopify's hosted checkout

Steps

  1. Create your products in Shopify

    In your Shopify Admin, go to Products and create a product for each item you want to sell. Give each product a title, description, images, and price. Shopify automatically generates a URL-safe handle from the product title — this becomes the slug for your product pages.

  2. Get a Storefront API access token

    The Storefront API uses a public access token — it's designed to be used in front-end code and only exposes data your store makes publicly available.

    1. In your Shopify Admin, go to Apps → Develop apps
    2. Click Create an app, give it a name (e.g. "ZeroPoint")
    3. Click Configure Storefront API scopes and enable unauthenticated_read_product_listings and unauthenticated_read_product_inventory
    4. Click Install app, then copy the Storefront API access token

    Store both your token and your store domain as environment variables in your deployment platform:

    • Netlify: Site configuration → Environment variables → Add variable
    • Cloudflare Pages: Settings → Environment variables → Add variable
    • GitHub Actions: Repository settings → Secrets and variables → Actions → New repository secret

    For local development, add both to a .env file at the root of your project (ensure .env is in your .gitignore):

    SHOPIFY_STORE_DOMAIN=your-store.myshopify.com
    SHOPIFY_STOREFRONT_TOKEN=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  3. Create a Shopify data file

    No npm package required — the Storefront API is a standard GraphQL endpoint over HTTPS, so native fetch is all you need. Create src/data/shopify.js to fetch your products at build time:

    src/data/shopify.js
    export default async function () {
      const query = `{
        products(first: 50, sortKey: TITLE) {
          edges {
            node {
              title
              handle
              description
              priceRange {
                minVariantPrice {
                  amount
                  currencyCode
                }
              }
              featuredImage {
                url
                altText
              }
              variants(first: 1) {
                edges {
                  node {
                    id
                  }
                }
              }
            }
          }
        }
      }`;
    
      const response = await fetch(
        `https://${process.env.SHOPIFY_STORE_DOMAIN}/api/2025-01/graphql.json`,
        {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            'X-Shopify-Storefront-Access-Token': process.env.SHOPIFY_STOREFRONT_TOKEN,
          },
          body: JSON.stringify({ query }),
        }
      );
    
      const { data } = await response.json();
    
      return data.products.edges.map(({ node }) => {
        // Variant IDs from Shopify are global IDs like "gid://shopify/ProductVariant/123"
        // We need just the numeric portion for the checkout URL
        const variantGid = node.variants.edges[0]?.node?.id ?? '';
        const variantId = variantGid.split('/').pop();
    
        const amount = parseFloat(node.priceRange.minVariantPrice.amount);
        const currency = node.priceRange.minVariantPrice.currencyCode;
    
        return {
          name: node.title,
          slug: node.handle, // Shopify handles are already URL-safe slugs
          description: node.description,
          image: node.featuredImage?.url ?? null,
          imageAlt: node.featuredImage?.altText ?? node.title,
          price: new Intl.NumberFormat('en-US', {
            style: 'currency',
            currency,
          }).format(amount),
          // Direct single-product checkout URL — no backend or cart needed
          checkoutUrl: `https://${process.env.SHOPIFY_STORE_DOMAIN}/cart/${variantId}:1/checkout`,
        };
      });
    }

    The shopify data is now available to all your ZeroPoint templates. Note that SHOPIFY_STORE_DOMAIN is used at build time only — it does not appear in the generated HTML (the checkout URL is baked in as a plain string).

  4. Create the store listing page

    Create src/content/pages/store.njk to render a grid of product cards, each linking to its own detail page:

    src/content/pages/store.njk
    ---
    title: Store
    permalink: /store/
    ---
    
    <h1>Store</h1>
    
    <div class="product-grid">
    {% for product in shopify %}
      <article class="product-card">
        {% if product.image %}
        <a href="/store/{{ product.slug }}/">
          <img src="{{ product.image }}" alt="{{ product.imageAlt }}">
        </a>
        {% endif %}
        <h2><a href="/store/{{ product.slug }}/">{{ product.name }}</a></h2>
        <p class="product-price">{{ product.price }}</p>
        <a href="/store/{{ product.slug }}/" class="button button-secondary">View Product</a>
      </article>
    {% endfor %}
    </div>
  5. Generate individual product pages

    Pagination can generate one page per item in a dataset. Create src/content/pages/store-product.njk — ZeroPoint will output a page at /store/your-product-handle/ for every product in your Shopify catalog:

    src/content/pages/store-product.njk
    ---
    pagination:
      data: shopify
      size: 1
      alias: product
    permalink: /store/{{ product.slug }}/
    eleventyExcludeFromCollections: true
    ---
    
    <nav class="breadcrumb">
      <a href="/store/">Back to Store</a>
    </nav>
    
    <article class="product-single">
      {% if product.image %}
      <div class="product-image">
        <img src="{{ product.image }}" alt="{{ product.imageAlt }}">
      </div>
      {% endif %}
    
      <div class="product-details">
        <h1>{{ product.name }}</h1>
    
        {% if product.description %}
        <div class="product-description">
          {{ product.description | markdown | safe }}
        </div>
        {% endif %}
    
        <p class="product-price">{{ product.price }}</p>
    
        <a href="{{ product.checkoutUrl }}" class="button button-primary">
          Buy Now &rarr;
        </a>
      </div>
    </article>

    Add a product in Shopify, trigger a build, and it gets a card on /store/ and its own page at /store/its-handle/. Archive a product in Shopify and both disappear on the next build.

Going Further