Building Scalable E-commerce with Astro and Saleor

Building Scalable E-commerce with Astro and Saleor

Learn how to build a fast, SEO-optimized e-commerce storefront using Astro static generation capabilities combined with Saleor headless commerce API.

Wojciech Gajda
Astro E-commerce Saleor Performance

Building Scalable E-commerce with Astro and Saleor

E-commerce performance matters more than ever. With Core Web Vitals affecting SEO rankings and user expectations at an all-time high, choosing the right tech stack can make or break your online store. Today, I’ll show you how Astro’s static generation capabilities combined with Saleor’s headless commerce API create a powerhouse combination for modern e-commerce.

Why Astro + Saleor?

Astro’s Advantages

Saleor’s Strengths

Project Setup

Let’s start by setting up our Astro project with the necessary dependencies:

npm create astro@latest astro-saleor-store
cd astro-saleor-store
npm install graphql graphql-request @astrojs/tailwind

Setting Up the Saleor Client

First, we’ll create a GraphQL client to interact with Saleor’s API:

// src/lib/saleor.ts
import { GraphQLClient } from 'graphql-request';

const SALEOR_API_URL = import.meta.env.PUBLIC_SALEOR_API_URL || 'https://demo.saleor.io/graphql/';

export const saleorClient = new GraphQLClient(SALEOR_API_URL, {
  headers: {
    'Content-Type': 'application/json',
  },
});

// GraphQL queries
export const GET_PRODUCTS = `
  query GetProducts($first: Int, $channel: String) {
    products(first: $first, channel: $channel) {
      edges {
        node {
          id
          name
          slug
          description
          thumbnail {
            url
            alt
          }
          pricing {
            priceRange {
              start {
                gross {
                  amount
                  currency
                }
              }
            }
          }
        }
      }
    }
  }
`;

export const GET_PRODUCT_BY_SLUG = `
  query GetProductBySlug($slug: String!, $channel: String) {
    product(slug: $slug, channel: $channel) {
      id
      name
      slug
      description
      images {
        url
        alt
      }
      variants {
        id
        name
        pricing {
          price {
            gross {
              amount
              currency
            }
          }
        }
      }
    }
  }
`;

Creating Product Pages with Static Generation

Now let’s create dynamic product pages that are statically generated at build time:

---
// src/pages/products/[slug].astro
import { saleorClient, GET_PRODUCT_BY_SLUG, GET_PRODUCTS } from '../../lib/saleor';
import Layout from '../../layouts/Layout.astro';

export async function getStaticPaths() {
  const data = await saleorClient.request(GET_PRODUCTS, {
    first: 100,
    channel: 'default-channel'
  });
  
  return data.products.edges.map(({ node }) => ({
    params: { slug: node.slug },
    props: { product: node }
  }));
}

const { slug } = Astro.params;
const product = await saleorClient.request(GET_PRODUCT_BY_SLUG, {
  slug,
  channel: 'default-channel'
});
---

<Layout title={product.product.name}>
  <main class="container mx-auto px-4 py-8">
    <div class="grid grid-cols-1 md:grid-cols-2 gap-8">
      <!-- Product Images -->
      <div class="space-y-4">
        {product.product.images.map((image) => (
          <img 
            src={image.url} 
            alt={image.alt}
            class="w-full rounded-lg shadow-lg"
          />
        ))}
      </div>
      
      <!-- Product Info -->
      <div class="space-y-6">
        <h1 class="text-3xl font-bold">{product.product.name}</h1>
        <p class="text-gray-600">{product.product.description}</p>
        
        <!-- Variants -->
        <div class="space-y-4">
          {product.product.variants.map((variant) => (
            <div class="border rounded-lg p-4">
              <h3 class="font-semibold">{variant.name}</h3>
              <p class="text-2xl font-bold text-green-600">
                ${variant.pricing.price.gross.amount}
              </p>
              <button class="add-to-cart bg-blue-600 text-white px-6 py-2 rounded-lg mt-2">
                Add to Cart
              </button>
            </div>
          ))}
        </div>
      </div>
    </div>
  </main>
</Layout>

<script>
  // Add interactive cart functionality
  document.querySelectorAll('.add-to-cart').forEach(button => {
    button.addEventListener('click', (e) => {
      // Add to cart logic here
      console.log('Added to cart!');
    });
  });
</script>

Building a Product Listing Page

Create a fast-loading product catalog:

---
// src/pages/products/index.astro
import { saleorClient, GET_PRODUCTS } from '../../lib/saleor';
import Layout from '../../layouts/Layout.astro';
import ProductCard from '../../components/ProductCard.astro';

const data = await saleorClient.request(GET_PRODUCTS, {
  first: 50,
  channel: 'default-channel'
});

const products = data.products.edges.map(edge => edge.node);
---

<Layout title="Products">
  <main class="container mx-auto px-4 py-8">
    <h1 class="text-4xl font-bold mb-8">Our Products</h1>
    
    <div class="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-6">
      {products.map((product) => (
        <ProductCard product={product} />
      ))}
    </div>
  </main>
</Layout>

Optimizing Performance

Image Optimization

---
// src/components/ProductCard.astro
import { Image } from 'astro:assets';

const { product } = Astro.props;
---

<div class="product-card border rounded-lg overflow-hidden shadow-lg hover:shadow-xl transition-shadow">
  <a href={`/products/${product.slug}`}>
    <Image
      src={product.thumbnail.url}
      alt={product.thumbnail.alt}
      width={300}
      height={300}
      class="w-full h-48 object-cover"
      loading="lazy"
    />
    <div class="p-4">
      <h3 class="font-semibold text-lg">{product.name}</h3>
      <p class="text-gray-600 text-sm">{product.description}</p>
      <p class="font-bold text-xl text-green-600 mt-2">
        ${product.pricing.priceRange.start.gross.amount}
      </p>
    </div>
  </a>
</div>

Partial Hydration for Cart

---
// src/components/Cart.astro
---

<div id="cart" class="cart-container">
  <!-- Static cart UI -->
  <div class="cart-icon">
    🛒 <span id="cart-count">0</span>
  </div>
</div>

<!-- Only hydrate the cart component -->
<script>
  import { CartManager } from '../scripts/cart.js';
  
  // Initialize cart functionality
  const cart = new CartManager();
  cart.init();
</script>

SEO and Meta Tags

---
// src/components/SEO.astro
const { title, description, image, url } = Astro.props;
---

<head>
  <title>{title}</title>
  <meta name="description" content={description} />
  
  <!-- Open Graph -->
  <meta property="og:title" content={title} />
  <meta property="og:description" content={description} />
  <meta property="og:image" content={image} />
  <meta property="og:url" content={url} />
  
  <!-- Twitter Card -->
  <meta name="twitter:card" content="summary_large_image" />
  <meta name="twitter:title" content={title} />
  <meta name="twitter:description" content={description} />
  <meta name="twitter:image" content={image} />
  
  <!-- Structured Data -->
  <script type="application/ld+json">
    {JSON.stringify({
      "@context": "https://schema.org",
      "@type": "Product",
      "name": title,
      "description": description,
      "image": image
    })}
  </script>
</head>

Performance Results

With this setup, you can expect:

Deployment Strategy

For optimal performance, deploy to edge locations:

# Vercel (recommended)
npm run build
vercel deploy

# Netlify
npm run build
netlify deploy --prod

# Cloudflare Pages
npm run build
wrangler pages publish dist

Conclusion

The combination of Astro and Saleor delivers exceptional performance while maintaining developer experience and business flexibility. By leveraging static generation for product pages and selective hydration for interactive components, you get the best of both worlds: fast loading times and rich user interactions.

This architecture scales beautifully and provides a solid foundation for enterprise e-commerce solutions.


Ready to build your own lightning-fast e-commerce store? Check out the complete source code on GitHub.