Remix ve Polaris ile Özel Shopify Uygulamaları Oluşturma
Shopify'ın varsayılan uygulama çerçevesi olarak Remix'e geçişi, üretim uygulamalarının platformda oluşturulma biçiminde temel bir değişime işaret ediyor. Remix tabanlı Shopify uygulama şablonu, tek bir tutarlı yığında sunucu tarafı oluşturma, akış, iç içe yönlendirme ve yerel Shopify CLI entegrasyonu sunar; 2023 yılına kadar uygulama geliştirmede hakim olan eski Node/Express + React modellerinin yerini alır.
Bu kılavuz, geliştirme yaşam döngüsünün tamamını ele alır: yapı iskelesi, kimlik doğrulama, GraphQL veri getirme, Polaris bileşen entegrasyonu, web kancaları, faturalandırma ve deneyimli Shopify geliştiricilerinin bakımı yapılabilir, performanslı yerleşik uygulamalar sunmak için kullandığı modeller.
Önemli Çıkarımlar
- Shopify CLI 3.x, önceden yapılandırılmış OAuth, oturum depolama ve Uygulama Köprüsü ile üretime hazır bir Remix uygulaması oluşturur
authenticate.adminyardımcısı OAuth akışının tamamını yönetir; OAuth'u manuel olarak uygulamayın- Remix'in yükleyici/eylem modeli, Shopify'ın GraphQL istek/mutasyon döngüsüyle mükemmel şekilde eşleşir
- App Store onayı için Polaris bileşenleri gereklidir — Polaris standartlarını göz ardı eden özel kullanıcı arayüzü incelemede başarısız olur
- Web kancalarının güvenilirlik açısından yalnızca API çağrıları değil, uygulama yapılandırması aracılığıyla da kaydedilmesi gerekir
- Shopify Admin GraphQL API sayfalara ayrılmıştır — veri yükleyicilerde her zaman imleç tabanlı sayfalandırmayı yönetin
- App Bridge, krom içermeyen yerleşik deneyim sağlar; gezinme için
useAppBridgekancasını kullanın- Abonelik ücreti alan tüm uygulamalar için Faturalandırma API entegrasyonu gereklidir
Proje Kurulumu ve İskele
Düzgün yapılandırılmış bir Remix uygulamasını oluşturmak için Shopify CLI 3 ile başlayın:
npm install -g @shopify/cli@latest
shopify app create node --template remix
cd your-app-name
Oluşturulan iskele şunları içerir:
- Shopify uyumlu ayarlarla
remix.config.js shopify.app.toml— uygulama yapılandırma dosyanız (uygulama ayarları için.envyerine geçer)app/shopify.server.ts— merkezi kimlik doğrulama ve API istemci yapılandırmasıapp/routes/app.tsx— kök yerleşik uygulama düzeniprisma/schema.prisma— SQLite oturum depolama alanı (üretim için PostgreSQL ile değiştirin)
shopify.app.toml yapısı:
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"
authenticate.admin ile kimlik doğrulama
shopify.server.ts dosyası, tüm kimlik doğrulama sorunlarını ele alan bir authenticate nesnesini dışa aktarır. OAuth'u hiçbir zaman manuel olarak uygulamayın; authenticate.admin yardımcısı aşağıdakiler de dahil olmak üzere tüm akışı yönetir:
- OAuth kod değişimi
- Oturum kalıcılığı
- Jeton yenileme
- Çevrimiçi ve çevrimdışı erişim modları
- Tüccar rızası toplama
Rota yükleyicilerde kimlik doğrulamayı kullanma:
// 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 });
};
Çevrimiçi ve çevrimdışı oturumlar:
- Çevrimdışı erişim: Çoğu uygulama için varsayılandır. Oturum, satıcı girişinden bağımsız olarak devam eder. Arka plan işleri, web kancaları ve zamanlanmış görevler için kullanılır.
- Çevrimiçi erişim: Oturum açmış satıcı kullanıcıyla bağlantılı oturum. Belirli bir kullanıcı adına hareket etmeniz gerektiğinde gereklidir (ör. hangi personelin bir işlemi gerçekleştirdiğini takip etmek).
shopify.server.ts'da yapılandırın:
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+)
},
});
Jeton Değişimi (2026 için önerilir)
Shopify'ın daha yeni Token Exchange kimlik doğrulama stratejisi, yerleşik uygulamalar için OAuth yönlendirmelerini ortadan kaldırır. Shopify yöneticisindeki kullanıcıların ayrılıp geri dönmeleri gerekmez; App Bridge'deki oturum jetonu doğrudan sunucu tarafında bir API erişim jetonuyla değiştirilir. Bunu unstable_newEmbeddedAuthStrategy: true ile etkinleştirin.
GraphQL Veri Alma Modelleri
Shopify'ın Yönetici API'si GraphQL önceliklidir. Üretim kalitesinde veri almak için bu modellerde ustalaşın.
İmleç tabanlı sayfalandırma:
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;
}
Hata işlemeyle ilgili mutasyonlar:
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 });
};
Büyük veri kümeleri için toplu işlemler:
Binlerce kayıttaki işlemler için sayfalara ayrılmış sorgular yerine Toplu İşlemler API'sini kullanın. Toplu sorgular eşzamansız olarak çalışır ve bir JSONL dosyası döndürür:
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
}
}
}
`);
currentBulkOperation'yi status === "COMPLETED"'ye kadar yoklayın, ardından JSONL'yi url'den indirin ve ayrıştırın.
Polaris Tasarım Sistemi Entegrasyonu
Polaris, Shopify'ın gömülü uygulamalara yönelik tasarım sistemidir. App Store gönderimleri için Polaris'in kullanılması gereklidir; Polaris olmayan kullanıcı arayüzü incelemenin reddedilmesine neden olur.
Remix'te Polaris'i kurma:
// 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>
);
}
Shopify uygulamaları için ortak Polaris bileşen kalıpları:
// 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>
);
}
Polaris ve Remix ile form kullanımı:
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>
);
}
Web Kancaları: Kayıt ve İşleme
Güvenilir webhook yönetimi, ticari mağaza etkinliklerine tepki veren uygulamalar için kritik öneme sahiptir.
Web kancalarını shopify.app.toml aracılığıyla kaydetme:
[[webhooks.subscriptions]]
topics = ["products/create", "products/update", "products/delete"]
uri = "/webhooks"
[[webhooks.subscriptions]]
topics = ["orders/create"]
uri = "/webhooks/orders"
Web kancalarını Remix rotasında işleme:
// 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 });
};
Webhook güvenilirliğine ilişkin en iyi uygulamalar:
- Hemen 200 yanıtı döndürün; işlem yavaşsa arka plan kuyruğu aracılığıyla eşzamansız olarak işleyin
- Webhook'un
X-Shopify-Webhook-Idbaşlığını kullanarak bağımsızlığı uygulayın - HMAC imzasını doğrulayın (
authenticate.webhooktarafından işlenir) - Yeniden deneme mantığını yönetin — Shopify, başarısız web kancalarını 48 saat içinde 19 defaya kadar yeniden dener
- Hata ayıklama için tüm web kancası yüklerini günlüğe kaydedin (günlüğe kaydetmeden önce PII'yi soyun)
Faturalandırma API'si: Abonelik ve Kullanım Ücretleri
Tüm uygulama ücretlerinde Shopify'ın Faturalandırma API'si kullanılmalıdır. Bunu atlamak App Store politikalarını ihlal eder.
Tek seferlik uygulama satın alma:
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 });
};
Faturalandırma planlarını shopify.server.ts'da yapılandırın:
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,
}
}
});
Uygulama Köprüsü ve Yerleşik Gezinme
App Bridge, Shopify yöneticisinde yerleşik iframe deneyimini yöneten JavaScript kitaplığıdır.
Önemli Uygulama Köprüsü modelleri:
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>
);
}
Test ve Yerel Kalkınma
# 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
GraphQL sorgularını test etme:
Sorguları kodda uygulamadan önce test etmek için geliştirme mağazası yöneticinizdeki Shopify GraphiQL uygulamasını kullanın. Erişim yolu: your-store.myshopify.com/admin/apps/graphiql.
Birim test rota yükleyicileri:
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");
});
});
Sıkça Sorulan Sorular
Yeni Shopify uygulamaları için Remix şablonunu mu yoksa Node/Express şablonunu mu kullanmalıyım?
Tüm yeni uygulamalar için Remix şablonunu kullanın. Shopify, uygulama geliştirme araçları için Remix'i standartlaştırmıştır; Shopify CLI, belgeler ve App Bridge React'ın tümü Remix için optimize edilmiştir. Node/Express şablonu artık aktif olarak geliştirilmemektedir ve Token Exchange dahil birçok modern kimlik doğrulama özelliğinden yoksundur. Mevcut Node/Express uygulamalarının hemen taşınması gerekmez ancak tüm yeni uygulamaların Remix ile başlaması gerekir.
Polaris yerine farklı bir ön uç çerçevesi kullanabilir miyim?
Uygulamanızın herkese açık bölümleri (kendi alan adınızdaki ayarlar sayfaları, açılış sayfaları vb.) için kendi tasarım sisteminizi kullanabilirsiniz ancak Shopify yöneticisi içindeki gömülü bölümlerde Polaris kullanılmalıdır. Uygulama inceleme yönergeleri, yerleşik kullanıcı arayüzü için açıkça Polaris'i gerektirir. Polaris'i özel bileşenlerle görsel olarak kopyalamaya çalışmak genellikle davranışsal ve erişilebilirlik tutarsızlıkları nedeniyle incelemenin reddedilmesiyle sonuçlanır.
Uygulamamda Shopify API ücret sınırlarını nasıl yönetirim?
Shopify'ın GraphQL Admin API'si bir "paket" hız sınırlama modeli kullanır. Her mağaza, uygulamanıza paket başına 1.000 maliyet puanı verir ve bu, saniyede 50 puanla yenilenir. GraphQL yanıtlarındaki extensions.cost.throttleStatus alanını izleyin. Toplu işlemler için (binlerce kaydı etkileyen), ayrı hız sınırları olan Toplu İşlemler API'sini kullanın. 429 yanıt aldığınızda titreşimle üstel geri çekilme uygulayın.
Shopify uygulamam için üretimde hangi veritabanını kullanmalıyım?
Üretim için varsayılan SQLite'ı PostgreSQL ile değiştirin. Prisma oturum depolama bağdaştırıcısı PostgreSQL ile çalışır; ortam değişkenlerinizdeki bağlantı dizesini değiştirin ve prisma/schema.prisma'yi postgresql sağlayıcısını kullanacak şekilde güncelleyin. Yüksek oturum hacmine sahip uygulamalarda, sıcak kimlik doğrulama yolundaki veritabanı yükünü azaltmak amacıyla oturum depolama için PostgreSQL yerine Redis'i düşünün.
Remix ile oluşturulmuş bir Shopify uygulamasını nasıl dağıtırım?
Shopify, Node.js çalışma zamanını destekleyen platformlarda dağıtım yapılmasını önerir: Fly.io, Render, Demiryolu, AWS (EC2/ECS) veya Google Cloud Run. Vercel ve Netlify, ön uç için çalışır ancak Shopify'ın oturum depolamasının gerektirdiği kalıcı Node.js sunucusunu çalıştıramaz. Dağıtım platformunuzun web kancası işleme ve arka plan işleri için uzun süren işlemleri desteklediğinden emin olun.
Sonraki Adımlar
Kimlik doğrulamayı, GraphQL veri yönetimini, web kancalarını ve faturalandırmayı doğru bir şekilde yöneten özel bir Shopify uygulaması oluşturmak, derin platform uzmanlığı gerektirir. Yanlış yapılandırılmış bir uygulama, App Store incelemesinde başarısız olur, ticari mağazaları çökertir veya güvenlik açıklarını açığa çıkarır.
ECOSIRE'ın Shopify uygulama geliştirme hizmetleri tüm geliştirme yaşam döngüsünü kapsar: mimari tasarım, Remix/Polaris uygulaması, GraphQL API entegrasyonu, webhook altyapısı, faturalandırma kurulumu, App Store gönderimi ve lansman sonrası destek.
Özel Shopify uygulaması gereksinimlerinizi tartışın geliştirme ekibimizle.
Yazan
ECOSIRE Research and Development Team
ECOSIRE'da kurumsal düzeyde dijital ürünler geliştiriyor. Odoo entegrasyonları, e-ticaret otomasyonu ve yapay zeka destekli iş çözümleri hakkında içgörüler paylaşıyor.
İlgili Makaleler
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.