Création d'applications Shopify personnalisées avec Remix et Polaris
La migration de Shopify vers Remix comme framework d'application par défaut marque un changement fondamental dans la façon dont les applications de production sont créées sur la plateforme. Le modèle d'application Shopify basé sur Remix offre un rendu côté serveur, un streaming, un routage imbriqué et une intégration native de Shopify CLI dans une seule pile cohérente, remplaçant les anciens modèles Node/Express + React qui dominaient le développement d'applications jusqu'en 2023.
Ce guide parcourt le cycle de vie complet du développement : échafaudage, authentification, récupération de données GraphQL, intégration de composants Polaris, webhooks, facturation et modèles expérimentés que les développeurs Shopify utilisent pour fournir des applications intégrées maintenables et performantes.
Points clés à retenir
- Shopify CLI 3.x génère une application Remix prête pour la production avec OAuth, stockage de session et App Bridge préconfigurés
- L'assistant
authenticate.admingère l'intégralité du flux OAuth — n'implémentez pas OAuth manuellement- Le modèle de chargeur/action de Remix correspond parfaitement au cycle de requête/mutation GraphQL de Shopify
- Les composants Polaris sont requis pour l'approbation de l'App Store – une interface utilisateur personnalisée qui ignore les normes Polaris échoue à l'examen
- Les Webhooks doivent être enregistrés via la configuration de l'application, et pas seulement les appels API, pour des raisons de fiabilité
- L'API Shopify Admin GraphQL est paginée — gérez toujours la pagination basée sur le curseur dans les chargeurs de données
- App Bridge offre une expérience intégrée sans chrome ; utilisez le crochet
useAppBridgepour la navigation- L'intégration de l'API de facturation est requise pour toute application facturant des frais d'abonnement
Configuration du projet et échafaudage
Commencez avec Shopify CLI 3 pour créer une application Remix correctement configurée :
npm install -g @shopify/cli@latest
shopify app create node --template remix
cd your-app-name
L'échafaudage généré comprend :
remix.config.jsavec paramètres compatibles Shopifyshopify.app.toml— le fichier de configuration de votre application (remplace.envpour les paramètres de l'application)app/shopify.server.ts— l'authentification centrale et la configuration du client APIapp/routes/app.tsx— la présentation de l'application racine intégréeprisma/schema.prisma— Stockage de session SQLite (remplacer par PostgreSQL pour la production)
shopify.app.tomlstructure :
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"
Authentification avec authenticate.admin
Le fichier shopify.server.ts exporte un objet authenticate qui gère tous les problèmes d'authentification. N'implémentez jamais OAuth manuellement : l'assistant authenticate.admin gère l'intégralité du flux, notamment :
- Échange de codes OAuth
- Persistance de session
- Actualisation du jeton
- Modes d'accès en ligne et hors ligne
- Collecte du consentement du commerçant
Utilisation de l'authentification dans les chargeurs de route :
// 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 });
};
Sessions en ligne et hors ligne :
- Accès hors ligne : valeur par défaut pour la plupart des applications. La session persiste indépendamment de la connexion du commerçant. Utilisé pour les tâches en arrière-plan, les webhooks et les tâches planifiées.
- Accès en ligne : Session liée à l'utilisateur marchand connecté. Obligatoire lorsque vous devez agir au nom d'un utilisateur spécifique (par exemple, pour savoir quel membre du personnel a effectué une action).
Configurer dans 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+)
},
});
Échange de jetons (recommandé pour 2026)
La nouvelle stratégie d'authentification Token Exchange de Shopify élimine les redirections OAuth pour les applications intégrées. Les utilisateurs de l'administrateur Shopify n'ont pas besoin de quitter et de revenir : le jeton de session d'App Bridge est échangé directement contre un jeton d'accès API côté serveur. Activez-le avec unstable_newEmbeddedAuthStrategy: true.
Modèles de récupération de données GraphQL
L'API d'administration de Shopify est d'abord GraphQL. Maîtrisez ces modèles pour une récupération de données de qualité production.
Papagation basée sur le curseur :
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;
}
Mutations avec gestion des erreurs :
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 });
};
Opérations groupées pour les grands ensembles de données :
Pour les opérations sur des milliers d’enregistrements, utilisez l’API Bulk Operations plutôt que les requêtes paginées. Les requêtes groupées s'exécutent de manière asynchrone et renvoient un fichier 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
}
}
}
`);
Interrogez currentBulkOperation jusqu'à status === "COMPLETED", puis téléchargez et analysez le JSONL à partir de url.
Intégration du système de conception Polaris
Polaris est le système de conception de Shopify pour les applications intégrées. L’utilisation de Polaris est requise pour les soumissions sur l’App Store – une interface utilisateur non Polaris entraînera le rejet de l’avis.
Configuration de Polaris dans 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>
);
}
Modèles de composants Polaris courants pour les applications 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>
);
}
Gestion des formulaires avec Polaris et 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 : inscription et traitement
Une gestion fiable des webhooks est essentielle pour les applications qui réagissent aux événements des magasins marchands.
Enregistrement des webhooks via shopify.app.toml :
[[webhooks.subscriptions]]
topics = ["products/create", "products/update", "products/delete"]
uri = "/webhooks"
[[webhooks.subscriptions]]
topics = ["orders/create"]
uri = "/webhooks/orders"
Traitement des webhooks dans une route 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 });
};
Bonnes pratiques en matière de fiabilité des webhooks :
- Renvoyez immédiatement une réponse 200 – traitez de manière asynchrone via une file d'attente en arrière-plan si l'opération est lente
- Implémentez l'idempotence à l'aide de l'en-tête
X-Shopify-Webhook-Iddu webhook - Vérifiez la signature HMAC (gérée par
authenticate.webhook) - Gérer la logique de nouvelle tentative – Shopify réessaye les webhooks ayant échoué jusqu'à 19 fois en 48 heures
- Enregistrez toutes les charges utiles du webhook pour le débogage (supprimez les PII avant la journalisation)
API de facturation : frais d'abonnement et d'utilisation
Tous les frais de facturation d'application doivent utiliser l'API de facturation de Shopify. Le contourner viole les politiques de l’App Store.
Achat unique de l'application :
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 });
};
Configurez les forfaits de facturation dans 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 et navigation intégrée
App Bridge est la bibliothèque JavaScript qui gère l'expérience iframe intégrée dans l'administrateur Shopify.
Modèles clés d'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>
);
}
Tests et développement 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
Test des requêtes GraphQL :
Utilisez l'application Shopify GraphiQL dans l'administrateur de votre boutique de développement pour tester les requêtes avant de les implémenter dans le code. Accès via : your-store.myshopify.com/admin/apps/graphiql.
Chargeurs de routes de tests unitaires :
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");
});
});
Questions fréquemment posées
Dois-je utiliser le modèle Remix ou le modèle Node/Express pour les nouvelles applications Shopify ?
Utilisez le modèle Remix pour toutes les nouvelles applications. Shopify a standardisé Remix pour ses outils de développement d'applications : la CLI, la documentation et App Bridge React de Shopify sont tous optimisés pour Remix. Le modèle Node/Express n'est plus activement développé et ne dispose pas de plusieurs fonctionnalités d'authentification modernes, notamment Token Exchange. Les applications Node/Express existantes n'ont pas besoin d'être migrées immédiatement, mais toutes les nouvelles applications doivent démarrer avec Remix.
Puis-je utiliser un autre framework frontend au lieu de Polaris ?
Vous pouvez utiliser votre propre système de conception pour les parties publiques de votre application (pages de paramètres sur votre propre domaine, pages de destination, etc.), mais les sections intégrées dans l'administrateur Shopify doivent utiliser Polaris. Les directives d'examen des applications nécessitent explicitement Polaris pour l'interface utilisateur intégrée. Tenter de reproduire visuellement Polaris avec des composants personnalisés entraîne généralement un rejet de l'avis en raison d'incohérences de comportement et d'accessibilité.
Comment gérer les limites de débit de l'API Shopify dans mon application ?
L'API GraphQL Admin de Shopify utilise un modèle de limitation de débit « seau ». Chaque boutique donne à votre application 1 000 points de coût par compartiment, qui se régénère à 50 points par seconde. Surveillez le champ extensions.cost.throttleStatus dans les réponses GraphQL. Pour les opérations groupées (affectant des milliers d’enregistrements), utilisez l’API Bulk Operations, qui a des limites de débit distinctes. Implémentez un recul exponentiel avec instabilité lorsque vous recevez 429 réponses.
Quelle base de données dois-je utiliser pour mon application Shopify en production ?
Remplacez le SQLite par défaut par PostgreSQL pour la production. L'adaptateur de stockage de session Prisma fonctionne avec PostgreSQL : modifiez la chaîne de connexion dans vos variables d'environnement et mettez à jour prisma/schema.prisma pour utiliser le fournisseur postgresql. Pour les applications avec des volumes de sessions élevés, envisagez Redis pour le stockage de sessions au lieu de PostgreSQL afin de réduire la charge de la base de données sur le chemin d'authentification à chaud.
Comment déployer une application Shopify créée avec Remix ?
Shopify recommande de déployer sur des plates-formes prenant en charge le runtime Node.js : Fly.io, Render, Railway, AWS (EC2/ECS) ou Google Cloud Run. Vercel et Netlify fonctionnent pour le frontend mais ne peuvent pas exécuter le serveur Node.js persistant requis par le stockage de session de Shopify. Assurez-vous que votre plate-forme de déploiement prend en charge les processus de longue durée pour le traitement des webhooks et les tâches en arrière-plan.
Prochaines étapes
Créer une application Shopify personnalisée qui gère correctement l'authentification, la gestion des données GraphQL, les webhooks et la facturation nécessite une expertise approfondie de la plate-forme. Une application mal configurée échoue à l’examen de l’App Store, interrompt les magasins marchands ou expose des vulnérabilités de sécurité.
Les services de développement d'applications Shopify d'ECOSIRE couvrent le cycle de vie complet du développement : conception de l'architecture, mise en œuvre de Remix/Polaris, intégration de l'API GraphQL, infrastructure de webhook, configuration de la facturation, soumission de l'App Store et assistance post-lancement.
Discutez des exigences de votre application Shopify personnalisée avec notre équipe de développement.
Rédigé par
ECOSIRE Research and Development Team
Création de produits numériques de niveau entreprise chez ECOSIRE. Partage d'analyses sur les intégrations Odoo, l'automatisation e-commerce et les solutions d'entreprise propulsées par l'IA.
Articles connexes
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.