medusajs/melhores-práticas-para-storefront

Sempre use esta habilidade ao trabalhar em lojas virtuais, sites de e-commerce e componentes de vitrine, incluindo checkout, carrinho, fluxos de pagamento, páginas de produto, listagens, navegação e home. Essencial para integrar backend Medusa, construir funcionalidades de e-commerce e implementar carrinho ou checkout. Agnóstico a frameworks (Next.js, SvelteKit, TanStack Start, React, Vue). Fornece padrões, frameworks de decisão e orientação de integração com backend.

O que é melhores-práticas-para-storefront?

Esta habilidade é um guia abrangente para desenvolvedores que constroem lojas virtuais modernas utilizando o Medusa como backend headless. Ela fornece padrões de implementação, frameworks de decisão e orientação técnica para todos os componentes de um storefront, independentemente do framework frontend escolhido (Next.js, SvelteKit, TanStack Start, React, Vue). A habilidade cobre desde a configuração inicial da loja até a implementação de funcionalidades críticas como carrinho de compras, fluxos de checkout completos (com guest checkout e login), páginas de produto e listagens otimizadas para SEO. Ela também aborda práticas essenciais como tratamento de erros, loading states, cacheamento de dados, estratégias de preços (com suporte a múltiplas moedas e regiões) e boas práticas de desempenho. O foco principal é integrar perfeitamente o frontend com as APIs do Medusa, utilizando o Medusa React para gerenciar estado, cache e requisições de forma eficiente. A habilidade é ideal para desenvolvedores que desejam acelerar o desenvolvimento de e-commerce com padrões testados e evitar armadilhas comuns.

Funciona com~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/medusajs/medusa-agent-skills/tree/main/skills/storefront-best-practices

Installed? Explore more Produtividade e Colaboração skills: steipete/gemini, steipete/gh-issues, steipete/skill-creator · View all 6 →

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

O que melhores-práticas-para-storefront faz?

Sempre use esta habilidade ao trabalhar em vitrines de e-commerce, lojas online, sites de compras. Use para QUALQUER componente de vitrine, incluindo páginas de checkout, carrinho, fluxos de pagamento, páginas de produto, listagens de produtos, navegação, página inicial ou QUALQUER página/componente em uma vitrine. CRÍTICO para adicionar checkout, implementar carrinho, integrar backend Medusa ou construir qualquer funcionalidade de e-commerce. Agnóstico de framework (Next.js, SvelteKit, TanStack Start, React, Vue). Fornece padrões, frameworks de decisão e orientação de integração com backend.

Visão Geral

Este guia consolida as melhores práticas para construir vitrines de e-commerce headless com Medusa. Ele complementa os guias oficiais do Medusa e oferece padrões prontos para produção, frameworks de decisão para lidar com trade-offs comuns e orientação de integração de backend. O objetivo é ajudar equipes de engenharia a criar experiências de compras de alta qualidade, com bom desempenho e fáceis de manter.

Público-Alvo

Desenvolvedores de frontend e full-stack que:

  • Estão construindo uma nova vitrine Medusa do zero
  • Estão migrando uma loja existente para Medusa
  • Precisam de orientação sobre padrões de implementação, desempenho ou acessibilidade
  • Querem entender como estruturar componentes de e-commerce complexos (carrinho, checkout, páginas de produto)

Como Usar Este Guia

Cada seção descreve uma área-chave de funcionalidade de e-commerce. Para sua tarefa atual, faça o seguinte:

  1. Navegue até a seção relevante
  2. Revise os padrões recomendados e frameworks de decisão
  3. Aplique os padrões ao seu código, adaptando conforme necessário para seu caso de uso específico

Índice

  1. Integração com Backend
  2. Estrutura de Loja
  3. Componentes de Vitrine
  4. Carrinho e Checkout
  5. Componentes de Produto
  6. Tratamento de Erros e Estados de Carregamento
  7. Desempenho e Cache
  8. Preços e Moeda
  9. Integrações de Pagamento
  10. SEO e Metadados
  11. Conclusão

1. Integração com Backend

1.1. Conectando ao Backend Medusa

Use o SDK oficial @medusajs/medusa-js ou o pacote React @medusajs/medusa-react para conectar sua vitrine ao backend Medusa.

Para projetos React/Next.js:

import { MedusaProvider } from 'medusa-react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { CartProvider, SessionProvider } from 'medusa-react'

const queryClient = new QueryClient()
const backUrl = process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL || 'http://localhost:9000'

function MyApp({ Component, pageProps }) {
  return (
    <MedusaProvider
      baseUrl={backUrl}
      queryClientProviderProps={{
        client: queryClient,
      }}
    >
      <SessionProvider>
        <CartProvider>
          <Component {...pageProps} />
        </CartProvider>
      </SessionProvider>
    </MedusaProvider>
  )
}

Para projetos não-React (Vanilla JS, Vue, Svelte, etc.):

import Medusa from '@medusajs/medusa-js'

const medusa = new Medusa({ baseUrl: 'http://localhost:9000', maxRetries: 3 })

// Exemplo: buscar produtos
const { products, count, limit, offset } = await medusa.products.list()

1.2. Gerenciamento de Estado do Cliente

Medusa usa sessões anônimas para rastrear o carrinho antes do login e carrinhos autenticados após o login.

Padrão: Sempre inicialize uma sessão de cliente anônima assim que o usuário carregar a página.

// React com medusa-react
import { useSession } from 'medusa-react'

function StorefrontApp() {
  const { customer, isLoading } = useSession()
  // customer é null para usuários anônimos
  // A sessão é gerenciada automaticamente pelo CartProvider
}

Práticas Recomendadas:

  • Não force login para navegar ou adicionar ao carrinho. Permita compras como convidado.
  • Armazene o ID de carrinho anônimo em cookie ou localStorage para que persista entre sessões.
  • Ao fazer login, mescle o carrinho anônimo com a conta do cliente (Medusa faz isso automaticamente via medusa.auth.createSession com callback).

1.3. Estratégias de Autenticação

Fluxo de Login Padrão:

  1. Usuário insere e-mail e senha
  2. Chamar medusa.auth.createSession para autenticar
  3. A sessão é armazenada automaticamente em cookie pelo backend
  4. O SessionProvider em medusa-react detecta o cookie e atualiza o estado do cliente

Registro de Novo Cliente:

const { customer } = await medusa.customers.create({
  email: '[email protected]',
  first_name: 'John',
  last_name: 'Doe',
  password: 'securepassword'
})
// Isso automaticamente cria e autentica uma sessão

1.4. Tratamento de Erros de API

Use um interceptador global ou wrapper para tratar erros de API de forma consistente:

async function fetchWithErrorHandling(url, options) {
  try {
    const response = await fetch(url, options)
    if (!response.ok) {
      const error = await response.json()
      throw new Error(error.message || 'Erro desconhecido')
    }
    return await response.json()
  } catch (error) {
    console.error('Erro na requisição:', error)
    // Notificar o usuário ou exibir estado de erro
    throw error
  }
}

2. Estrutura de Loja

2.1. Arquitetura de Páginas Recomendada

Organize sua vitrine com uma estrutura de páginas clara. Exemplo para Next.js:

/pages
  _app.js          - Provedores globais (MedusaProvider, CartProvider, SessionProvider)
  index.js         - Página inicial (produtos em destaque, hero, etc.)
  /products
    index.js       - Listagem de produtos (grade, filtros, paginação)
    [handle].js    - Página de detalhes do produto
  /cart
    index.js       - Página do carrinho (itens, quantidades, subtotal, cupom)
  /checkout
    index.js       - Fluxo de checkout (endereço, envio, pagamento, confirmação)
  /account
    index.js       - Painel da conta do cliente (pedidos, detalhes)

2.2. Contextos e Provedores

Gerencie estado global com provedores React (ou equivalentes em outros frameworks):

// Em _app.js
function MyApp({ Component, pageProps }) {
  return (
    <MedusaProvider baseUrl={MEDUSA_BACKEND_URL}>
      <SessionProvider>
        <CartProvider>
          <Component {...pageProps} />
        </CartProvider>
      </SessionProvider>
    </MedusaProvider>
  )
}

Funções dos Provedores:

  • MedusaProvider: Fornece o cliente Medusa e integra React Query para cache
  • SessionProvider: Gerencia estado de sessão anônima vs autenticada
  • CartProvider: Gerencia estado do carrinho (conteúdo, quantidades, etc.)

3. Componentes de Vitrine

3.1. Componentes Reutilizáveis vs Específicos

Componentes Reutilizáveis:

  • Botões (add-to-cart, checkout, etc.)
  • Campos de formulário (quantidade, variante)
  • Cards de produto (imagem, título, preço, ação)
  • Componentes de navegação (menu, breadcrumbs)

Componentes Específicos de Página:

  • Página de listagem de produtos (busca, filtros, grade)
  • Página de detalhes do produto (galeria, informações, seleção de variante)
  • Fluxo de checkout (multi-etapas, validação)

3.2. Padrão para Componentes de Produto

Cada componente de produto deve ser o mais granular possível para reutilização:

// ProductCard.jsx
import { useCart } from 'medusa-react'

export default function ProductCard({ product }) {
  const { addItem } = useCart()
  
  const handleAddToCart = () => {
    // Usar a primeira variante disponível ou a selecionada
    const variant = product.variants[0]
    addItem({
      variant_id: variant.id,
      quantity: 1
    })
  }
  
  return (
    <div className="product-card">
      <Image src={product.thumbnail} alt={product.title} />
      <h3>{product.title}</h3>
      <p>{product.description}</p>
      <button onClick={handleAddToCart}>Adicionar ao Carrinho</button>
    </div>
  )
}

4. Carrinho e Checkout

4.1. Gerenciamento de Carrinho

Use useCart() hook do medusa-react para gerenciar estado do carrinho:

function CartPage() {
  const { cart, addItem, updateItem, removeItem, setCart } = useCart()
  
  // cart.items contém todos os itens do carrinho
  // cart.total é o total calculado (inclui impostos, envio)
  
  const handleUpdateQuantity = (itemId, quantity) => {
    updateItem({ line_id: itemId, quantity })
  }
  
  const handleRemoveItem = (itemId) => {
    removeItem({ line_id: itemId })
  }
  
  if (!cart) return <p>Carregando carrinho...</p>
  if (cart.items.length === 0) return <p>Seu carrinho está vazio.</p>
  
  return (
    <div>
      {cart.items.map(item => (
        <CartItem
          key={item.id}
          item={item}
          onUpdateQuantity={handleUpdateQuantity}
          onRemove={handleRemoveItem}
        />
      ))}
      <p>Subtotal: {cart.subtotal}</p>
      <p>Total: {cart.total}</p>
      <Link href="/checkout">Ir para Checkout</Link>
    </div>
  )
}

4.2. Fluxo de Checkout

Etapas Recomendadas:

  1. Endereço de Envio - Coletar e validar endereço
  2. Método de Envio - Selecionar opção de envio disponível
  3. Pagamento - Selecionar método de pagamento e inserir detalhes
  4. Revisão - Revisar pedido antes de finalizar
  5. Confirmação - Confirmar pedido e exibir ID do pedido

Exemplo de Criação de Pedido (última etapa do checkout):

import { useCart, useMeCustomer } from 'medusa-react'
import { useRouter } from 'next/router'

function CheckoutConfirmation() {
  const { cart, completeCart } = useCart()
  const router = useRouter()
  
  const handlePlaceOrder = async () => {
    try {
      const { order, type } = await completeCart()
      
      if (type === 'order') {
        // Pedido criado com sucesso
        router.push(`/order/confirmed/${order.id}`)
      } else if (type === 'cart') {
        // Cart ainda não está pronto para checkout (exige mais informações)
        console.log('Carrinho incompleto:', cart)
      }
    } catch (error) {
      console.error('Falha ao finalizar pedido:', error)
      // Exibir mensagem de erro ao usuário
    }
  }
  
  return (
    <div>
      <h2>Revisar Pedido</h2>
      <p>Endereço de Envio: {cart.shipping_address?.address_1}</p>
      <p>Método de Envio: {cart.shipping_methods?.[0]?.name}</p>
      <p>Total: {cart.total}</p>
      <button onClick={handlePlaceOrder}>Finalizar Pedido</button>
    </div>
  )
}

4.3. Cupons de Desconto

Permita que os usuários apliquem cupons no carrinho:

function CouponField() {
  const { cart, updateCart } = useCart()
  const [couponCode, setCouponCode] = useState('')
  
  const handleApplyCoupon = () => {
    updateCart({
      discounts: [{ code: couponCode }]
    })
    setCouponCode('')
  }
  
  return (
    <div>
      <input
        type="text"
        value={couponCode}
        onChange={(e) => setCouponCode(e.target.value)}
        placeholder="Digite seu cupom"
      />
      <button onClick={handleApplyCoupon}>Aplicar</button>
    </div>
  )
}

5. Componentes de Produto

5.1. Listagem de Produtos

Paginação e Filtros:

import { useProducts } from 'medusa-react'
import { useState } from 'react'

function ProductListing({ collectionHandle }) {
  const [page, setPage] = useState(0)
  const PAGE_SIZE = 12
  
  const { products, count, isLoading } = useProducts({
    limit: PAGE_SIZE,
    offset: page * PAGE_SIZE,
    collection_id: [collectionHandle] // ou use collection_id
  })
  
  const totalPages = Math.ceil(count / PAGE_SIZE)
  
  if (isLoading) return <p>Carregando produtos...</p>
  
  return (
    <div>
      <div className="product-grid">
        {products?.map(product => (
          <ProductCard key={product.id} product={product} />
        ))}
      </div>
      <div className="pagination">
        {Array.from({ length: totalPages }, (_, i) => (
          <button key={i} onClick={() => setPage(i)} disabled={i === page}>
            {i + 1}
          </button>
        ))}
      </div>
    </div>
  )
}

5.2. Página de Detalhes do Produto

Seleção de Variante e Galeria:

import { useProduct } from 'medusa-react'
import { useState } from 'react'

function ProductDetails({ handle }) {
  const { product, isLoading } = useProduct(handle)
  const [selectedVariant, setSelectedVariant] = useState(null)
  
  if (isLoading) return <p>Carregando produto...</p>
  if (!product) return <p>Produto não encontrado.</p>
  
  // Usar a primeira variante se nenhuma estiver selecionada
  const variant = selectedVariant || product.variants[0]
  
  return (
    <div>
      <div className="product-gallery">
        <img src={variant.thumbnail || product.thumbnail} alt={product.title} />
        {/* Miniaturas de imagens */}
      </div>
      <div className="product-info">
        <h1>{product.title}</h1>
        <p>{product.description}</p>
        <p className="price">{variant.prices[0].amount} {variant.prices[0].currency_code}</p>
        <div className="variant-selector">
          {product.variants.map(v => (
            <button
              key={v.id}
              onClick={() => setSelectedVariant(v)}
              className={v.id === variant.id ? 'selected' : ''}
            >
              {v.title}
            </button>
          ))}
        </div>
        <AddToCartButton variant={variant} />
      </div>
    </div>
  )
}

6. Tratamento de Erros e Estados de Carregamento

6.1. Padrão de Carregamento

Sempre forneça um feedback visual durante carregamentos de dados:

function ProductPage() {
  const { product, isLoading, isError } = useProduct(handle)
  
  if (isLoading) return <Skeleton />
  if (isError) return <ErrorState message="Falha ao carregar produto" />
  if (!product) return <NotFound />
  
  return <ProductDetails product={product} />
}

6.2. Tratamento de Erros Específicos

Erros de Estoque:

function AddToCartButton({ variant }) {
  const { cart, addItem } = useCart()
  const [error, setError] = useState(null)
  
  const handleAddToCart = async () => {
    try {
      if (variant.inventory_quantity <= 0) {
        throw new Error('Produto fora de estoque')
      }
      await addItem({
        variant_id: variant.id,
        quantity: 1
      })
    } catch (err) {
      setError(err.message)
    }
  }
  
  return (
    <div>
      <button onClick={handleAddToCart}>
        {variant.inventory_quantity > 0 ? 'Adicionar ao Carrinho' : 'Fora de Estoque'}
      </button>
      {error && <p className="error">{error}</p>}
    </div>
  )
}

7. Desempenho e Cache

7.1. Estratégia de Cache com Medusa React

Medusa React usa React Query (TanStack Query) que gerencia cache automático:

// Configurações de cache
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 1000 * 60 * 5, // 5 minutos antes de revalidar
      cacheTime: 1000 * 60 * 30, // Armazenar em cache por 30 minutos
      refetchOnWindowFocus: false, // Evitar refetch em foco de janela
    },
  },
})

7.2. Otimização de Imagens

  • Use Image component com lazy loading
  • Sirva imagens via CDN (Medusa aceita URLs externas)
  • Defina tamanhos adequados no thumbnail e imagens
import Image from 'next/image'

function ProductImage({ src, alt, width, height }) {
  return (
    <Image
      src={src || '/placeholder.png'}
      alt={alt}
      width={width || 400}
      height={height || 400}
      lazy="loading"
      priority={false}
    />
  )
}

7.3. Prefetching

Para navegação instantânea, prefetch dados de páginas que o usuário provavelmente visitará:

import { useQueryClient } from '@tanstack/react-query'

function ProductCard({ product }) {
  const queryClient = useQueryClient()
  
  const prefetchProduct = () => {
    queryClient.prefetchQuery({
      queryKey: ['product', product.handle],
      queryFn: () => medusa.products.retrieve(product.handle)
    })
  }
  
  return (
    <Link
      href={`/products/${product.handle}`}
      onMouseEnter={prefetchProduct}
    >
      <h3>{product.title}</h3>
    </Link>
  )
}

8. Preços e Moeda

8.1. Exibição de Preço

Medusa suporta múltiplas moedas e regiões. Sempre exiba o preço no formato apropriado:

function PriceDisplay({ amount, currencyCode }) {
  const formattedPrice = new Intl.NumberFormat('pt-BR', {
    style: 'currency',
    currency: currencyCode || 'BRL'
  }).format(amount / 100) // Medusa armazena preços em centavos
  
  return <span>{formattedPrice}</span>
}

8.2. Preços com Desconto

Se houver um preço original e um preço promocional, exiba ambos:

function PriceWithDiscount({ price, originalPrice, currencyCode }) {
  const discountPercentage = Math.round((1 - price / originalPrice) * 100)
  
  return (
    <div>
      <span className="original-price" style={{ textDecoration: 'line-through' }}>
        <PriceDisplay amount={originalPrice} currencyCode={currencyCode} />
      </span>
      <span className="sale-price">
        <PriceDisplay amount={price} currencyCode={currencyCode} />
      </span>
      {discountPercentage > 0 && <span className="discount-badge">-{discountPercentage}%</span>}
    </div>
  )
}

9. Integrações de Pagamento

9.1. Configuração de Métodos de Pagamento

Medusa suporta vários provedores de pagamento (Stripe, PayPal, etc.). Configure no backend:

// No arquivo medusa-config.js
const plugins = [
  {
    resolve: `medusa-payment-stripe`,
    options: {
      api_key: process.env.STRIPE_API_KEY,
      webhook_secret: process.env.STRIPE_WEBHOOK_SECRET,
    },
  },
]

9.2. Coleta de Pagamento no Frontend

Para Stripe, use o Stripe Elements ou Stripe Checkout:

import { CardElement, useStripe, useElements } from '@stripe/react-stripe-js'

function PaymentForm() {
  const stripe = useStripe()
  const elements = useElements()
  
  const handleSubmit = async (event) => {
    event.preventDefault()
    
    if (!stripe || !elements) return
    
    const { error, paymentMethod } = await stripe.createPaymentMethod({
      type: 'card',
      card: elements.getElement(CardElement),
    })
    
    if (error) {
      console.error('Erro de pagamento:', error)
    } else {
      // Enviar paymentMethod.id para o backend Medusa
      await medusa.carts.setPaymentSession(cart.id, {
        provider_id: 'stripe',
        data: { payment_method_id: paymentMethod.id }
      })
    }
  }
  
  return (
    <form onSubmit={handleSubmit}>
      <CardElement />
      <button type="submit" disabled={!stripe}>
        Pagar
      </button>
    </form>
  )
}

10. SEO e Metadados

10.1. Metadados por Página

Use tags <head> apropriadas para SEO, Open Graph e Twitter Cards:

import Head from 'next/head'

function ProductPage({ product }) {
  return (
    <>
      <Head>
        <title>{product.title} | Minha Loja</title>
        <meta name="description" content={product.description} />
        <meta property="og:title" content={product.title} />
        <meta property="og:description" content={product.description} />
        <meta property="og:image" content={product.thumbnail} />
        <meta name="twitter:card" content="summary_large_image" />
      </Head>
      {/* Conteúdo da página */}
    </>
  )
}

10.2. URLs Amigáveis

Use handle (slug) do produto para URLs limpas e amigáveis:

// Em Next.js, use [handle].js
// URL: /products/camiseta-basica

11. Conclusão

Este guia fornece as bases para construir uma vitrine Medusa performática e fácil de manter. Lembre-se:

  • Consulte sempre as referências oficiais do Medusa para detalhes atualizados
  • Adapte os padrões ao seu framework e caso de uso
  • Teste fluxos de checkout e pagamento completamente
  • Monitore desempenho e otimize conforme necessário

Para mais informações, visite docs.medusajs.com.

Individual skills in this repo

This repo contains 7 individual skills — each has its own dedicated page.

medusajs/building-admin-dashboard-customizations

Load automatically when planning, researching, or implementing Medusa Admin dashboard UI (widgets, custom pages, forms, tables, data loading, navigation). REQUIRED for all admin UI work in ALL modes (planning, implementation, exploration). Contains design patterns, component usage, and data loading patterns that MCP servers don't provide.

medusajs/building-storefronts

Load automatically when planning, researching, or implementing Medusa storefront features (calling custom API routes, SDK integration, React Query patterns, data fetching). REQUIRED for all storefront development in ALL modes (planning, implementation, exploration). Contains SDK usage patterns, frontend integration, and critical rules for calling Medusa APIs.

medusajs/building-with-medusa

Load automatically when planning, researching, or implementing ANY Medusa backend features (custom modules, API routes, workflows, data models, module links, business logic). REQUIRED for all Medusa backend work in ALL modes (planning, implementation, exploration). Contains architectural patterns, best practices, and critical rules that MCP servers don't provide.

medusajs/db-generate

Generate database migrations for a Medusa module

medusajs/db-migrate

Run database migrations in Medusa

medusajs/learning-medusa

Load automatically when user asks to learn Medusa development (e.g., "teach me how to build with medusa", "guide me through medusa", "I want to learn medusa"). Interactive guided tutorial where Claude acts as a coding bootcamp instructor, teaching step-by-step with checkpoints and verification.

medusajs/new-user

Create an admin user in Medusa

Habilidades Relacionadas