Construindo aplicativos personalizados do Shopify com Remix e Polaris
A migração do Shopify para o Remix como estrutura de aplicativo padrão marca uma mudança fundamental na forma como os aplicativos de produção são construídos na plataforma. O modelo de aplicativo Shopify baseado em Remix oferece renderização no lado do servidor, streaming, roteamento aninhado e integração CLI nativa do Shopify em uma única pilha coerente – substituindo os antigos padrões Node/Express + React que dominaram o desenvolvimento de aplicativos até 2023.
Este guia percorre todo o ciclo de vida de desenvolvimento: estrutura, autenticação, busca de dados GraphQL, integração de componentes Polaris, webhooks, faturamento e os padrões que desenvolvedores experientes do Shopify usam para fornecer aplicativos incorporados de alto desempenho e manutenção.
Principais conclusões
- Shopify CLI 3.x gera um aplicativo Remix pronto para produção com OAuth, armazenamento de sessão e App Bridge pré-configurados
- O auxiliar
authenticate.adminlida com todo o fluxo do OAuth — não implemente o OAuth manualmente- O padrão de carregamento/ação do Remix mapeia perfeitamente para o ciclo de solicitação/mutação GraphQL do Shopify
- Os componentes Polaris são necessários para aprovação da App Store – UI personalizada que ignora os padrões Polaris falha na revisão
- Webhooks devem ser registrados por meio da configuração do aplicativo, não apenas por chamadas de API, para maior confiabilidade
- A API GraphQL Admin do Shopify é paginada — sempre processe a paginação baseada em cursor em carregadores de dados
- App Bridge fornece uma experiência incorporada sem cromo; use o gancho
useAppBridgepara navegação- A integração da API de faturamento é necessária para qualquer aplicativo que cobre taxas de assinatura
Configuração e andaimes do projeto
Comece com Shopify CLI 3 para criar um aplicativo Remix configurado corretamente:
npm install -g @shopify/cli@latest
shopify app create node --template remix
cd your-app-name
O andaime gerado inclui:
remix.config.jscom configurações compatíveis com Shopifyshopify.app.toml— o arquivo de configuração do seu aplicativo (substitui.envpelas configurações do aplicativo)app/shopify.server.ts— a autenticação central e configuração do cliente APIapp/routes/app.tsx— o layout raiz do aplicativo incorporadoprisma/schema.prisma— armazenamento de sessão SQLite (substitua por PostgreSQL para produção)
Estrutura shopify.app.toml:
name = "your-app-name"
client_id = "your-api-key"
application_url = "https://your-app-url.com"
embedded = true
[access_scopes]
scopes = "read_products,write_products,read_orders"
[auth]
redirect_urls = ["https://your-app-url.com/auth/callback"]
[webhooks]
api_version = "2025-01"
[[webhooks.subscriptions]]
topics = ["app/uninstalled"]
uri = "/webhooks"
Autenticação com authenticate.admin
O arquivo shopify.server.ts exporta um objeto authenticate que trata de todas as questões de autenticação. Nunca implemente o OAuth manualmente — o auxiliar authenticate.admin gerencia todo o fluxo, incluindo:
- Troca de código OAuth
- Persistência de sessão
- Atualização de token
- Modos de acesso online vs offline
- Coleta de consentimento do comerciante
Usando autenticação em carregadores de rota:
// app/routes/app.products.tsx
import { json } from "@remix-run/node";
import { authenticate } from "../shopify.server";
export const loader = async ({ request }: LoaderFunctionArgs) => {
const { admin, session } = await authenticate.admin(request);
const response = await admin.graphql(`
#graphql
query GetProducts($first: Int!) {
products(first: $first) {
nodes {
id
title
status
priceRangeV2 {
minVariantPrice {
amount
currencyCode
}
}
totalInventory
}
pageInfo {
hasNextPage
endCursor
}
}
}
`, {
variables: { first: 50 }
});
const data = await response.json();
return json({ products: data.data.products });
};
Sessões on-line versus off-line:
- Acesso off-line: padrão para a maioria dos aplicativos. A sessão persiste independentemente do login do comerciante. Usado para trabalhos em segundo plano, webhooks e tarefas agendadas.
- Acesso online: Sessão vinculada ao usuário comerciante logado. Obrigatório quando você precisa agir em nome de um usuário específico (por exemplo, rastrear qual membro da equipe executou uma ação).
Configurar em shopify.server.ts:
const shopify = shopifyApp({
apiKey: process.env.SHOPIFY_API_KEY!,
apiSecretKey: process.env.SHOPIFY_API_SECRET!,
scopes: process.env.SCOPES!.split(","),
appUrl: process.env.SHOPIFY_APP_URL!,
authPathPrefix: "/auth",
sessionStorage: new PrismaSessionStorage(prisma),
distribution: AppDistribution.AppStore,
future: {
unstable_newEmbeddedAuthStrategy: true, // Token Exchange (2024+)
},
});
Troca de tokens (recomendado para 2026)
A estratégia de autenticação Token Exchange mais recente do Shopify elimina redirecionamentos OAuth para aplicativos incorporados. Os usuários do admin do Shopify não precisam sair e retornar — o token de sessão do App Bridge é trocado diretamente por um token de acesso à API no lado do servidor. Habilite isso com unstable_newEmbeddedAuthStrategy: true.
Padrões de busca de dados GraphQL
A API Admin do Shopify prioriza o GraphQL. Domine esses padrões para obter dados com qualidade de produção.
Paginação baseada em cursor:
export async function getAllProducts(admin: AdminApiContext) {
let hasNextPage = true;
let cursor: string | null = null;
const allProducts = [];
while (hasNextPage) {
const response = await admin.graphql(`
#graphql
query GetProducts($first: Int!, $after: String) {
products(first: $first, after: $after) {
nodes {
id
title
handle
}
pageInfo {
hasNextPage
endCursor
}
}
}
`, {
variables: { first: 250, after: cursor }
});
const data = await response.json();
const { nodes, pageInfo } = data.data.products;
allProducts.push(...nodes);
hasNextPage = pageInfo.hasNextPage;
cursor = pageInfo.endCursor;
}
return allProducts;
}
Mutações com tratamento de erros:
export const action = async ({ request }: ActionFunctionArgs) => {
const { admin } = await authenticate.admin(request);
const formData = await request.formData();
const title = formData.get("title") as string;
const price = formData.get("price") as string;
const response = await admin.graphql(`
#graphql
mutation CreateProduct($input: ProductInput!) {
productCreate(input: $input) {
product {
id
title
}
userErrors {
field
message
}
}
}
`, {
variables: {
input: {
title,
variants: [{ price }]
}
}
});
const data = await response.json();
if (data.data.productCreate.userErrors.length > 0) {
return json({
errors: data.data.productCreate.userErrors
}, { status: 422 });
}
return json({ product: data.data.productCreate.product });
};
Operações em massa para grandes conjuntos de dados:
Para operações em milhares de registros, use a API de operações em massa em vez de consultas paginadas. Consultas em massa são executadas de forma assíncrona e retornam um arquivo JSONL:
const bulkQuery = await admin.graphql(`
mutation {
bulkOperationRunQuery(
query: """
{
products {
edges {
node {
id
title
variants {
edges {
node {
id
price
inventoryQuantity
}
}
}
}
}
}
}
"""
) {
bulkOperation {
id
status
}
userErrors {
field
message
}
}
}
`);
Pesquise currentBulkOperation até status === "COMPLETED" e, em seguida, baixe e analise o JSONL de url.
Integração do sistema de design Polaris
Polaris é o sistema de design do Shopify para aplicativos incorporados. O uso do Polaris é obrigatório para envios da App Store – uma interface de usuário que não seja Polaris resultará na rejeição da revisão.
Configurando Polaris no Remix:
// app/root.tsx
import { AppProvider } from "@shopify/polaris";
import "@shopify/polaris/build/esm/styles.css";
import translations from "@shopify/polaris/locales/en.json";
export default function App() {
return (
<AppProvider i18n={translations}>
<Outlet />
</AppProvider>
);
}
Padrões comuns de componentes Polaris para apps da Shopify:
// Product listing with DataTable
import {
Page,
Card,
DataTable,
Button,
Badge,
Filters,
EmptyState
} from "@shopify/polaris";
export default function ProductsPage() {
const { products } = useLoaderData<typeof loader>();
const rows = products.nodes.map(product => [
product.title,
<Badge tone={product.status === 'ACTIVE' ? 'success' : 'warning'}>
{product.status}
</Badge>,
product.totalInventory,
`${product.priceRangeV2.minVariantPrice.currencyCode} ${product.priceRangeV2.minVariantPrice.amount}`,
<Button url={`/app/products/${product.id}`}>Edit</Button>
]);
return (
<Page
title="Products"
primaryAction={{ content: "Add product", url: "/app/products/new" }}
>
<Card>
<DataTable
columnContentTypes={['text', 'text', 'numeric', 'numeric', 'text']}
headings={['Title', 'Status', 'Inventory', 'Price', 'Actions']}
rows={rows}
/>
</Card>
</Page>
);
}
Manuseio de formulários com Polaris e Remix:
import { Form, useNavigation, useActionData } from "@remix-run/react";
import { TextField, Select, FormLayout, Banner } from "@shopify/polaris";
export default function ProductForm() {
const actionData = useActionData<typeof action>();
const navigation = useNavigation();
const isSubmitting = navigation.state === "submitting";
return (
<Form method="post">
<FormLayout>
{actionData?.errors && (
<Banner tone="critical">
{actionData.errors.map(e => <p key={e.field}>{e.message}</p>)}
</Banner>
)}
<TextField
label="Product title"
name="title"
autoComplete="off"
/>
<TextField
label="Price"
name="price"
type="number"
prefix="$"
autoComplete="off"
/>
<Button submit loading={isSubmitting}>Save product</Button>
</FormLayout>
</Form>
);
}
Webhooks: registro e processamento
O manuseio confiável de webhook é fundamental para aplicativos que reagem a eventos de lojas comerciais.
Registrando webhooks via shopify.app.toml:
[[webhooks.subscriptions]]
topics = ["products/create", "products/update", "products/delete"]
uri = "/webhooks"
[[webhooks.subscriptions]]
topics = ["orders/create"]
uri = "/webhooks/orders"
Processando webhooks em uma rota Remix:
// app/routes/webhooks.tsx
import { authenticate } from "../shopify.server";
import db from "../db.server";
export const action = async ({ request }: ActionFunctionArgs) => {
const { topic, shop, session, payload } = await authenticate.webhook(request);
switch (topic) {
case "PRODUCTS_CREATE":
await db.product.create({
data: {
shopifyId: payload.id.toString(),
title: payload.title,
shop: shop,
}
});
break;
case "APP_UNINSTALLED":
if (session) {
await db.session.deleteMany({ where: { shop } });
}
break;
default:
throw new Response("Unhandled webhook topic", { status: 404 });
}
return new Response(null, { status: 200 });
};
Práticas recomendadas de confiabilidade do webhook:
- Retorne uma resposta 200 imediatamente – processe de forma assíncrona por meio de uma fila em segundo plano se a operação for lenta
- Implemente a idempotência usando o cabeçalho
X-Shopify-Webhook-Iddo webhook - Verifique a assinatura HMAC (tratada por
authenticate.webhook) - Lidar com a lógica de repetição - o Shopify tenta novamente webhooks com falha até 19 vezes em 48 horas
- Registre todas as cargas úteis do webhook para depuração (remova o PII antes de registrar)
API de faturamento: cobranças de assinatura e uso
Quaisquer taxas de cobrança de aplicativos devem usar a API Billing da Shopify. Ignorá-lo viola as políticas da App Store.
Compra única de aplicativo:
export const loader = async ({ request }: LoaderFunctionArgs) => {
const { billing, session } = await authenticate.admin(request);
const { hasActivePayment, appSubscription } = await billing.check({
plans: ["Professional Plan"],
isTest: process.env.NODE_ENV !== "production",
});
if (!hasActivePayment) {
await billing.request({
plan: "Professional Plan",
isTest: process.env.NODE_ENV !== "production",
});
}
return json({ session });
};
Configure planos de faturamento em shopify.server.ts:
const shopify = shopifyApp({
// ...other config
billing: {
"Professional Plan": {
amount: 29.99,
currencyCode: "USD",
interval: BillingInterval.Every30Days,
},
"Enterprise Plan": {
amount: 99.99,
currencyCode: "USD",
interval: BillingInterval.Every30Days,
}
}
});
App Bridge e navegação incorporada
App Bridge é a biblioteca JavaScript que gerencia a experiência iframe incorporada no admin do Shopify.
Padrões principais do App Bridge:
import { useAppBridge } from "@shopify/app-bridge-react";
import { Redirect } from "@shopify/app-bridge/actions";
// Navigate to an external URL from embedded context
function ExternalLinkButton() {
const app = useAppBridge();
const handleClick = () => {
const redirect = Redirect.create(app);
redirect.dispatch(Redirect.Action.REMOTE, {
url: "https://your-docs-site.com",
newContext: true
});
};
return <Button onClick={handleClick}>View documentation</Button>;
}
// Toast notifications
import { useToast } from "@shopify/app-bridge-react";
function SaveButton() {
const { show } = useToast();
const fetcher = useFetcher();
useEffect(() => {
if (fetcher.state === "idle" && fetcher.data?.success) {
show("Settings saved");
}
}, [fetcher.state, fetcher.data]);
return (
<Button
onClick={() => fetcher.submit(formData, { method: "post" })}
loading={fetcher.state !== "idle"}
>
Save
</Button>
);
}
Teste e Desenvolvimento Local
# Start the app in development mode
shopify app dev
# The CLI handles:
# - ngrok tunnel creation for webhook delivery
# - App installation on your development store
# - Hot module replacement for Remix routes
# - Shopify Partner Dashboard app URL updates
Testando consultas GraphQL:
Use o app Shopify GraphiQL no administrador da sua loja de desenvolvimento para testar consultas antes de implementá-las no código. Acesso via: your-store.myshopify.com/admin/apps/graphiql.
Carregadores de rotas de teste unitário:
import { describe, it, expect, vi } from "vitest";
import { loader } from "~/routes/app.products";
vi.mock("~/shopify.server", () => ({
authenticate: {
admin: vi.fn().mockResolvedValue({
admin: {
graphql: vi.fn().mockResolvedValue({
json: () => Promise.resolve({
data: {
products: {
nodes: [{ id: "gid://shopify/Product/1", title: "Test Product" }],
pageInfo: { hasNextPage: false, endCursor: null }
}
}
})
})
},
session: { shop: "test-shop.myshopify.com" }
})
}
}));
describe("Products loader", () => {
it("returns product list", async () => {
const request = new Request("https://test.com/app/products");
const response = await loader({ request, params: {}, context: {} });
const data = await response.json();
expect(data.products.nodes).toHaveLength(1);
expect(data.products.nodes[0].title).toBe("Test Product");
});
});
Perguntas frequentes
Devo usar o modelo Remix ou o modelo Node/Express para novos aplicativos da Shopify?
Use o modelo Remix para todos os novos aplicativos. Shopify padronizou o Remix para suas ferramentas de desenvolvimento de aplicativos – a CLI do Shopify, a documentação e o App Bridge React são todos otimizados para o Remix. O modelo Node/Express não é mais desenvolvido ativamente e carece de vários recursos de autenticação modernos, incluindo Token Exchange. Os aplicativos Node/Express existentes não precisam ser migrados imediatamente, mas todos os novos aplicativos devem começar com o Remix.
Posso usar uma estrutura de front-end diferente em vez do Polaris?
Você pode usar seu próprio sistema de design para as partes públicas do seu aplicativo (páginas de configurações em seu próprio domínio, páginas de destino etc.), mas as seções incorporadas no admin da Shopify devem usar Polaris. As diretrizes de revisão de aplicativos exigem explicitamente o Polaris para interface de usuário incorporada. A tentativa de replicar o Polaris visualmente com componentes personalizados normalmente resulta na rejeição da revisão devido a inconsistências comportamentais e de acessibilidade.
Como faço para lidar com os limites de taxa da API Shopify no meu aplicativo?
A API GraphQL Admin do Shopify usa um modelo de limitação de taxa de "balde". Cada loja oferece ao seu aplicativo 1.000 pontos de custo por balde, que regenera 50 pontos por segundo. Monitore o campo extensions.cost.throttleStatus nas respostas do GraphQL. Para operações em massa (afetando milhares de registros), use a API de operações em massa, que possui limites de taxa separados. Implemente a espera exponencial com jitter ao receber 429 respostas.
Qual banco de dados devo usar para meu aplicativo Shopify em produção?
Substitua o SQLite padrão pelo PostgreSQL para produção. O adaptador de armazenamento de sessão Prisma funciona com PostgreSQL — altere a string de conexão em suas variáveis de ambiente e atualize prisma/schema.prisma para usar o provedor postgresql. Para aplicativos com altos volumes de sessão, considere o Redis para armazenamento de sessão em vez do PostgreSQL para reduzir a carga do banco de dados no caminho de autenticação quente.
Como implantar um aplicativo Shopify criado com Remix?
A Shopify recomenda a implantação em plataformas compatíveis com o tempo de execução do Node.js: Fly.io, Render, Railway, AWS (EC2/ECS) ou Google Cloud Run. Vercel e Netlify funcionam para o frontend, mas não podem executar o servidor Node.js persistente exigido pelo armazenamento de sessão do Shopify. Certifique-se de que sua plataforma de implantação ofereça suporte a processos de longa execução para processamento de webhook e trabalhos em segundo plano.
Próximas etapas
Construir um aplicativo Shopify personalizado que lide corretamente com autenticação, gerenciamento de dados GraphQL, webhooks e faturamento requer profundo conhecimento de plataforma. Um aplicativo mal configurado falha na revisão da App Store, quebra lojas comerciais ou expõe vulnerabilidades de segurança.
Os serviços de desenvolvimento de aplicativos Shopify da ECOSIRE cobrem todo o ciclo de vida de desenvolvimento: design de arquitetura, implementação de Remix/Polaris, integração de API GraphQL, infraestrutura de webhook, configuração de faturamento, envio de App Store e suporte pós-lançamento.
Discuta os requisitos do seu aplicativo personalizado da Shopify com nossa equipe de desenvolvimento.
Escrito por
ECOSIRE Research and Development Team
Construindo produtos digitais de nível empresarial na ECOSIRE. Compartilhando insights sobre integrações Odoo, automação de e-commerce e soluções de negócios com IA.
Artigos Relacionados
Case Study: eCommerce Migration to Shopify with Odoo Backend
How a fashion retailer migrated from WooCommerce to Shopify and connected it to Odoo ERP, cutting order fulfillment time by 71% and growing revenue 43%.
Integrating GoHighLevel CRM with eCommerce Stores
Step-by-step guide to integrating GoHighLevel CRM with Shopify and WooCommerce. Sync orders, automate post-purchase flows, and recover abandoned carts at scale.
Odoo + Shopify Sync: Products, Orders, and Inventory
Complete guide to syncing Odoo 19 with Shopify. Covers product sync, real-time order import, bidirectional inventory, financial reconciliation, and multi-store management.