mcpField
S
shopify-app-development

shopify-app-development

github.com/finsilabs开发6076 次安装5 天前更新
代码仓库

关于 shopify-app-development

面向 Shopify 应用开发者的技能,指导使用 Shopify CLI 3.x 的 Remix 模板构建应用,涵盖嵌入式应用、OAuth、Admin API 和 Polaris 界面。

主要功能

  1. 01Remix 应用脚手架
  2. 02OAuth 与会话处理
  3. 03Admin API 访问
  4. 04Polaris 界面构建指南
  5. 05Webhook 与部署指南

适用场景

  1. 01启动公共或自定义 Shopify 应用
  2. 02构建商家使用的嵌入式应用
  3. 03添加经身份验证的 Admin API 操作
  4. 04将 Express 应用迁移到现代架构

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill shopify-app-development

安装后在 Claude Code 中输入/shopify-app-development确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

tile.json
evals/embedded-ui-with-polaris-and-app-bridge/
criteria.json
task.md
evals/gdpr-webhooks-and-hmac-validation/
criteria.json
task.md
evals/production-session-storage-and-api-versi/
criteria.json
task.md

详细介绍

Shopify 应用开发

概述

使用 Shopify CLI 3.x Remix 模板构建 Shopify 应用。该模板会自动处理 OAuth 令牌交换、会话存储和 App Bridge 初始化。嵌入式应用运行在 Shopify Admin iframe 中,并使用 Polaris 打造原生般的界面。现代方案使用基于 Remix 的 @shopify/shopify-app-remix 包,而不是旧版 Express 模板。

何时使用此技能

  • 构建扩展 Admin 功能的公共或自定义 Shopify 应用时
  • 创建商家从 Shopify App Store 安装的嵌入式应用时
  • 首次实现 OAuth,并在重新安装后持久保留会话时
  • 需要代表已认证的商家访问 Admin API 时
  • 使用 Shopify 的 Polaris 设计系统构建面向商家的工具时
  • 将较旧的 Express/koa Shopify 应用迁移到现代 Remix 技术栈时

核心说明

  1. 使用 Shopify CLI 搭建应用

    npm install -g @shopify/cli @shopify/theme
    shopify app init my-shopify-app
    # Choose: Remix template
    cd my-shopify-app
    shopify app dev
    

    这会搭建一个 Remix 应用,并预先配置 OAuth、会话存储(默认使用 SQLite)和 App Bridge。开发命令会通过 Cloudflare 为本地服务建立隧道,并将应用安装到 Partner 开发商店中。

  2. 了解 OAuth 流程和会话处理

    搭建的应用使用 @shopify/shopify-app-remix 处理 OAuth 流程。在 app/shopify.server.ts 中:

    import "@shopify/shopify-app-remix/adapters/node";
    import {
      AppDistribution,
      DeliveryMethod,
      shopifyApp,
      LATEST_API_VERSION,
    } from "@shopify/shopify-app-remix/server";
    import { PrismaSessionStorage } from "@shopify/shopify-app-session-storage-prisma";
    import { PrismaClient } from "@prisma/client";
    
    const prisma = new PrismaClient();
    
    const shopify = shopifyApp({
      apiKey: process.env.SHOPIFY_API_KEY,
      apiSecretKey: process.env.SHOPIFY_API_SECRET || "",
      apiVersion: LATEST_API_VERSION,
      scopes: process.env.SCOPES?.split(","),
      appUrl: process.env.SHOPIFY_APP_URL || "",
      authPathPrefix: "/auth",
      sessionStorage: new PrismaSessionStorage(prisma),
      distribution: AppDistribution.AppStore,
      webhooks: {
        APP_UNINSTALLED: {
          deliveryMethod: DeliveryMethod.Http,
          callbackUrl: "/webhooks",
        },
      },
      hooks: {
        afterAuth: async ({ session }) => {
          shopify.registerWebhooks({ session });
        },
      },
    });
    
    export default shopify;
    export const authenticate = shopify.authenticate;
    
  3. 保护路由并调用 Admin API

    任何需要访问 Admin API 的 loader 或 action 都会调用 authenticate.admin:

    // app/routes/app._index.tsx
    import { json } from "@remix-run/node";
    import { useLoaderData } from "@remix-run/react";
    import { authenticate } from "../shopify.server";
    
    export const loader = async ({ request }: LoaderFunctionArgs) => {
      const { admin, session } = await authenticate.admin(request);
    
      // GraphQL Admin API call
      const response = await admin.graphql(`
        query {
          shop {
            name
            email
            primaryDomain { url }
          }
        }
      `);
      const { data } = await response.json();
      return json({ shop: data.shop });
    };
    
    export default function Index() {
      const { shop } = useLoaderData<typeof loader>();
      return <Page title={`Hello, ${shop.name}`} />;
    }
    
  4. 使用 Polaris 组件构建 UI

    // app/routes/app.products.tsx
    import {
      Page,
      Layout,
      Card,
      DataTable,
      Button,
      Banner,
    } from "@shopify/polaris";
    import { TitleBar, useAppBridge } from "@shopify/app-bridge-react";
    
    export default function ProductsPage() {
      const shopify = useAppBridge();
    
      const handleSave = async () => {
        // Use App Bridge Toast for notifications inside the iframe
        shopify.toast.show("Products updated successfully");
      };
    
      return (
        <Page>
          <TitleBar title="Products" primaryAction={{ content: "Save", onAction: handleSave }} />
          <Layout>
            <Layout.Section>
              <Card>
                <DataTable
                  columnContentTypes={["text", "numeric", "numeric"]}
                  headings={["Product", "Price", "Inventory"]}
                  rows={[["Widget A", "$19.99", 42]]}
                />
              </Card>
            </Layout.Section>
          </Layout>
        </Page>
      );
    }
    
  5. 配置作用域并处理应用重新安装

    在 shopify.app.toml 中定义所需作用域:

    name = "my-shopify-app"
    client_id = "your_api_key"
    application_url = "https://your-app.fly.dev"
    embedded = true
    
    [access_scopes]
    scopes = "read_products,write_products,read_orders"
    
    [webhooks]
    api_version = "2025-01"
    
    [[webhooks.subscriptions]]
    topics = ["app/uninstalled"]
    uri = "/webhooks"
    

    处理 GDPR 强制要求的 webhook(customers/data_request、customers/redact、shop/redact),即使应用不存储个人数据也不例外——Shopify 要求提供这些端点。

  6. 部署到生产环境

    shopify app deploy
    # Deploys to Shopify (functions/extensions)
    # Deploy the Remix server separately (Fly.io, Railway, Render)
    fly launch
    fly deploy
    

示例

通过 Admin GraphQL API 执行变更
// Create a product via the Admin API inside a Remix action
export const action = async ({ request }: ActionFunctionArgs) => {
  const { admin } = await authenticate.admin(request);

  const response = await admin.graphql(
    `#graphql
    mutation CreateProduct($input: ProductInput!) {
      productCreate(input: $input) {
        product {
          id
          title
          handle
        }
        userErrors {
          field
          message
        }
      }
    }`,
    {
      variables: {
        input: {
          title: "New Product",
          vendor: "My Store",
          productType: "Widget",
          tags: ["new", "featured"],
        },
      },
    }
  );

  const { data } = await response.json();
  if (data.productCreate.userErrors.length > 0) {
    return json({ errors: data.productCreate.userErrors }, { status: 422 });
  }
  return json({ product: data.productCreate.product });
};
App Bridge 资源选择器(v4)
import { useAppBridge } from "@shopify/app-bridge-react";
import { useState } from "react";
import { Button } from "@shopify/polaris";

export default function ProductSelector() {
  const shopify = useAppBridge();
  const [selected, setSelected] = useState<string[]>([]);

  const handleSelectProducts = async () => {
    const selection = await shopify.resourcePicker({
      type: "product",
      multiple: true,
    });

    if (selection) {
      setSelected(selection.map((p) => p.id));
    }
  };

  return (
    <>
      <Button onClick={handleSelectProducts}>Select Products</Button>
      <p>Selected IDs: {selected.join(", ")}</p>
    </>
  );
}

最佳实践

  • 使用 Remix CLI 模板 — 它会自动处理会话存储、CSRF、OAuth 令牌刷新和 frame-ancestor CSP 标头
  • 将会话存储在持久化数据库中(生产环境使用 Prisma + PostgreSQL)— 默认的 SQLite 存储不适用于多实例部署
  • 过度申请作用域会降低转化率 — 仅申请所需的最小作用域;商家在安装期间会看到作用域列表
  • 使用 App Bridge 进行导航和显示模态框 — 直接使用 window.location 导航会破坏嵌入式 iframe 上下文
  • 验证 webhook HMAC 签名 — 即使是不会采取任何操作的 GDPR 强制要求 webhook,也要验证
  • 测试重新安装流程 — 商家卸载并重新安装后,必须获得新的 OAuth 令牌,且不能残留旧会话数据
  • 仅在开发环境中使用 LATEST_API_VERSION — 在生产环境中固定到特定版本(例如 2025-01),以避免发生破坏性变更
  • 处理 payment_required 错误 — 如果商家超出其套餐限制,App Store 中的应用可能会遇到账单要求错误

常见问题

问题解决方案
“Refused to display in frame” CSP 错误确保 Remix 服务在 Content-Security-Policy 中返回 frame-ancestors https://*.myshopify.com https://admin.shopify.com
安装后 OAuth 重定向循环检查 shopify.app.toml 中的应用 URL 是否与服务可访问的 URL 一致——不匹配会导致无限重定向
后续请求中找不到会话使用持久化会话存储(Prisma/PostgreSQL);SQLite 无法在 Fly.io 或 Render 实例之间使用
App Bridge 的 useAppBridge() 返回 null使用 @shopify/shopify-app-remix/react 中的 <AppProvider> 包裹 Remix 应用根组件
已注册 Webhook,但未触发在 afterAuth 期间注册的 Webhook 可能不会在应用更新后保留——在单独的路由中调用 shopify.registerWebhooks 进行验证
Webhook 端点出现“Invalid HMAC”确保在任何 JSON 解析中间件运行前读取原始请求正文——在 Express/Remix 解析请求正文前使用 getRawBody

相关技能

  • @shopify-admin-api
  • @shopify-webhooks
  • @shopify-checkout-extensions
  • @shopify-storefront-api
  • @oauth-implementation

评论

支持 Markdown