Skip to content
advanced Phase 115 · Headless

Headless Frontend for Magento 2

React/Vue storefront development, API integration, and SSR/SSG implementation

45m
2 problems
Topic Progress 0%

React Storefront

Project Setup

Next.js + Magento

# Create Next.js project
npx create-next-app magento-storefront
cd magento-storefront

# Install dependencies
npm install @apollo/client graphql
npm install @magento/venia-ui  # Optional: use Venia components

GraphQL Client Configuration

// lib/apollo-client.ts
import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';

const client = new ApolloClient({
  link: new HttpLink({
    uri: process.env.NEXT_PUBLIC_MAGENTO_GRAPHQL,
    headers: {
      'Store': 'default'
    }
  }),
  cache: new InMemoryCache(),
  defaultOptions: {
    watchQuery: {
      fetchPolicy: 'cache-and-network',
    },
  },
});

export default client;

Product Listing Component

// components/ProductList.tsx
import { useQuery, gql } from '@apollo/client';

const GET_PRODUCTS = gql`
  query GetProducts($pageSize: Int!) {
    products(filter: {}, pageSize: $pageSize) {
      items {
        id
        name
        sku
        url_key
        price_range {
          minimum_price {
            regular_price { value currency }
          }
        }
        image {
          url
        }
      }
    }
  }
`;

export default function ProductList({ pageSize = 20 }) {
  const { loading, error, data } = useQuery(GET_PRODUCTS, {
    variables: { pageSize }
  });

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div className="grid grid-cols-4 gap-4">
      {data.products.items.map((product: any) => (
        <div key={product.id} className="border rounded p-4">
          <img src={product.image.url} alt={product.name} />
          <h3>{product.name}</h3>
          <p>${product.price_range.minimum_price.regular_price.value}</p>
        </div>
      ))}
    </div>
  );
}

Product Detail Page

// pages/product/[sku].tsx
import { useRouter } from 'next/router';
import { useQuery, gql } from '@apollo/client';

const GET_PRODUCT = gql`
  query GetProduct($sku: String!) {
    products(filter: { sku: { eq: $sku } }) {
      items {
        name
        sku
        description { html }
        price_range {
          minimum_price {
            regular_price { value currency }
          }
        }
        media_gallery {
          url
          label
        }
      }
    }
  }
`;

export default function ProductPage() {
  const router = useRouter();
  const { sku } = router.query;
  
  const { loading, error, data } = useQuery(GET_PRODUCT, {
    variables: { sku },
    skip: !sku
  });

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error</div>;

  const product = data.products.items[0];

  return (
    <div>
      <h1>{product.name}</h1>
      <div dangerouslySetInnerHTML={{ __html: product.description.html }} />
      <button>Add to Cart</button>
    </div>
  );
}

Vue Storefront

Vue Storefront Setup

Installation

# Using Vue Storefront
npx @vue-storefront/cli init magento-storefront
cd magento-storefront

# Configure Magento connection
# config/magento.js
module.exports = {
  magento: {
    url: 'https://magento-store.com',
    graphql: '/graphql',
    store: 'default'
  }
};

Composable Pattern

// composables/useProduct.ts
import { useQuery } from '@vue/apollo-composable';
import { gql } from 'graphql-tag';

const GET_PRODUCTS = gql`
  query GetProducts($search: String, $pageSize: Int) {
    products(search: $search, pageSize: $pageSize) {
      items {
        id
        name
        sku
        price_range {
          minimum_price {
            regular_price { value }
          }
        }
      }
    }
  }
`;

export function useProduct(search?: string) {
  const { result, loading, error } = useQuery(GET_PRODUCTS, {
    search,
    pageSize: 20
  });

  return {
    products: computed(() => result.value?.products?.items ?? []),
    loading,
    error
  };
}

Product Card Component

<!-- components/ProductCard.vue -->
<template>
  <div class="product-card">
    <img :src="product.image.url" :alt="product.name" />
    <h3>{{ product.name }}</h3>
    <p class="price">${{ product.price_range.minimum_price.regular_price.value }}</p>
    <button @click="addToCart">Add to Cart</button>
  </div>
</template>

<script setup lang="ts">
import { useCart } from '@/composables/useCart';

const props = defineProps({
  product: { type: Object, required: true }
});

const { addItem } = useCart();

const addToCart = () => {
  addItem({
    sku: props.product.sku,
    quantity: 1
  });
};
</script>

SSR and SSG

Server-Side Rendering

Next.js SSR

// pages/products.tsx
import { GetServerSideProps } from 'next';
import { ApolloClient, InMemoryCache, gql } from '@apollo/client';

export const getServerSideProps: GetServerSideProps = async ({ req }) => {
  const client = new ApolloClient({
    uri: process.env.MAGENTO_GRAPHQL,
    cache: new InMemoryCache()
  });

  const { data } = await client.query({
    query: gql`
      query {
        products(pageSize: 20) {
          items { id name sku }
        }
      }
    `
  });

  return {
    props: {
      products: data.products.items
    }
  };
};

export default function Products({ products }) {
  return (
    <div>
      {products.map(p => <div key={p.id}>{p.name}</div>)}
    </div>
  );
}

Static Site Generation

// pages/product/[sku].tsx
import { GetStaticPaths, GetStaticProps } from 'next';
import { ApolloClient, InMemoryCache, gql } from '@apollo/client';

export const getStaticPaths: GetStaticPaths = async () => {
  const client = new ApolloClient({
    uri: process.env.MAGENTO_GRAPHQL,
    cache: new InMemoryCache()
  });

  const { data } = await client.query({
    query: gql`
      query {
        products(pageSize: 1000) {
          items { sku }
        }
      }
    `
  });

  const paths = data.products.items.map((product: any) => ({
    params: { sku: product.sku }
  }));

  return { paths, fallback: 'blocking' };
};

export const getStaticProps: GetStaticProps = async ({ params }) => {
  const { sku } = params;
  
  const client = new ApolloClient({
    uri: process.env.MAGENTO_GRAPHQL,
    cache: new InMemoryCache()
  });

  const { data } = await client.query({
    query: gql`
      query GetProduct($sku: String!) {
        products(filter: { sku: { eq: $sku } }) {
          items { name sku description { html } }
        }
      }
    `,
    variables: { sku }
  });

  return {
    props: { product: data.products.items[0] },
    revalidate: 3600 // ISR: revalidate every hour
  };
};

ISR (Incremental Static Regeneration)

// Revalidate static pages periodically
export const getStaticProps: GetStaticProps = async () => {
  // Fetch data
  return {
    props: { data },
    revalidate: 60 // Revalidate every 60 seconds
  };
};

// Benefits:
- Static performance
- Dynamic updates
- No rebuild required

Authentication & State

Authentication

JWT Token Flow

// lib/auth.ts
export async function login(email: string, password: string) {
  const response = await fetch(process.env.MAGENTO_GRAPHQL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      query: `mutation {
        generateCustomerToken(email: "${email}", password: "${password}") {
          token
        }
      }`
    })
  });

  const { data } = await response.json();
  return data.generateCustomerToken.token;
}

// Store token in httpOnly cookie
export async function setAuthCookie(token: string) {
  document.cookie = `auth_token=${token}; path=/; secure; httponly`;
}

Cart State Management

// stores/cart.ts (using Zustand)
import create from 'zustand';
import { gql } from '@apollo/client';

const CREATE_CART = gql`
  mutation CreateCart {
    createEmptyCart
  }
`;

const ADD_TO_CART = gql`
  mutation AddToCart($cartId: String!, $sku: String!, $qty: Int!) {
    addSimpleProductsToCart(
      input: {
        cart_id: $cartId
        cart_items: [{ data: { quantity: $qty, sku: $sku } }]
      }
    ) {
      cart {
        id
        items { sku quantity }
        prices { grand_total { value } }
      }
    }
  }
`;

interface CartState {
  cartId: string | null;
  items: Array<{ sku: string; quantity: number }>;
  createCart: () => Promise<void>;
  addItem: (sku: string, qty: number) => Promise<void>;
}

export const useCartStore = create<CartState>((set, get) => ({
  cartId: null,
  items: [],
  
  createCart: async () => {
    const { data } = await client.mutate({ mutation: CREATE_CART });
    set({ cartId: data.createEmptyCart });
  },
  
  addItem: async (sku, qty) => {
    let { cartId } = get();
    if (!cartId) {
      await get().createCart();
      cartId = get().cartId;
    }
    
    const { data } = await client.mutate({
      mutation: ADD_TO_CART,
      variables: { cartId, sku, qty }
    });
    
    set({ items: data.addSimpleProductsToCart.cart.items });
  }
}));

SSR Authentication

// Protected page with SSR
export const getServerSideProps: GetServerSideProps = async ({ req }) => {
  const token = req.cookies.auth_token;
  
  if (!token) {
    return { redirect: { destination: '/login', permanent: false } };
  }

  // Verify token and get user data
  const user = await verifyToken(token);
  
  return {
    props: { user }
  };
};

Practice Problems

0 / 2 solved
Product Listing PWA

Build a product listing page with React, GraphQL, and infinite scroll.

SSR Product Page

Implement server-side rendering for product pages with ISR.

Quiz

1. What is the difference between SSR and SSG?

Question 1 options

2. What is ISR?

Question 2 options

3. What state management works well with headless?

Question 3 options

4. How to authenticate in headless Magento?

Question 4 options

Flashcards

Question

What is SSR?

Answer

Server-Side Rendering: renders page on each request

Question

What is SSG?

Answer

Static Site Generation: generates pages at build time

Question

What is ISR?

Answer

Incremental Static Regeneration: static pages update periodically

Question

How to authenticate headless?

Answer

JWT tokens via GraphQL mutation

Question

What is Apollo Client?

Answer

GraphQL client for React with caching and state management

Revision Notes

Key Takeaways

  • 1. React/Vue with Apollo Client for GraphQL integration
  • 2. SSR: renders on request (good for dynamic content)
  • 3. SSG: generates at build time (best performance)
  • 4. ISR: static with periodic revalidation (best of both)
  • 5. JWT tokens for authentication in SPAs
  • 6. Zustand/Redux/Context for state management

Interview Tips

  • How do you set up a React storefront with Magento?
  • Explain SSR vs SSG vs ISR
  • How do you handle authentication in a headless app?
  • Describe your state management approach
  • How do you optimize performance in a headless frontend?

Cheat Sheet

Headless Frontend Cheat Sheet

Stack:

  • React/Vue + Next.js/Nuxt.js
  • Apollo Client for GraphQL
  • Zustand/Redux for state

Rendering:

  • SSR: dynamic, per-request
  • SSG: static, build-time
  • ISR: static + revalidation

Auth:

  • JWT via GraphQL mutation
  • httpOnly cookies
  • Token refresh

State:

  • Cart: server-side (Magento)
  • UI: client-side (Zustand)
  • User: JWT + context

Performance:

  • Code splitting
  • Lazy loading
  • CDN caching
  • Image optimization