agentsclimarketplace

Shopify app

Skill mentilead/shopify-app-skill/skills/shopify-app

Production patterns for embedded Shopify apps with React Router v7, DynamoDB single-table design, Lambda, SQS, CDK. Covers Polaris UI, Shopify GraphQL API, App Proxy, billing, webhooks, and CDK infrastructure.From its SKILL.md

Install
npx -y skills add mentilead/shopify-app-skill --skill shopify-app

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 5 stars5 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

9.3 KB, as published. Nobody here has run it

CRITICAL GOTCHAS (Top 10)

1. Never Use Polaris url Prop for Internal Navigation

// WRONG — breaks out of iframe
<Button url="/app/pricing">Upgrade</Button>

// CORRECT — stays in iframe
const navigate = useNavigate();
<Button onClick={() => navigate('/app/pricing')}>Upgrade</Button>

Rule: url prop is only safe for external URLs. Use useNavigate() for internal routes.

2. Never Use NODE_ENV for Billing isTest Flag

// WRONG — always false on Lambda (staging included)
isTest: process.env.NODE_ENV !== 'production'

// CORRECT — detect via Shopify API
isTest: await isDevelopmentStore(admin)

3. useFetcher Data Goes to fetcher.data, Not useActionData()

// WRONG — always undefined when using fetcher
const actionData = useActionData<typeof action>();

// CORRECT — read from fetcher
const fetcher = useFetcher();
const fetcherData = fetcher.data as { ok?: boolean; error?: string } | undefined;

4. useState Doesn't Sync with Loader After Save

// WRONG — state diverges from loader after save
const [brandColor, setBrandColor] = useState(settings.brandColor);

// CORRECT — sync from loader when data changes
const lastSyncRef = useRef(settings.updatedAt);
useEffect(() => {
  if (settings.updatedAt !== lastSyncRef.current) {
    setBrandColor(settings.brandColor);
    lastSyncRef.current = settings.updatedAt;
  }
}, [settings.updatedAt, settings.brandColor]);

5. Parent + Child Loaders Run in Parallel (Billing Race Condition)

// CORRECT — redirect after billing sync to force clean second load
const url = new URL(request.url);
if (url.searchParams.has('charge_id')) {
  url.searchParams.delete('charge_id');
  throw redirect(url.pathname + url.search);
}

6. App Proxy: GET Only, No External CSS/JS

ConstraintDetail
GET onlyProxy only forwards GET. POST must go directly to the app URL.
No external CSS/JS<link> and <script src> resolve against storefront domain and 404.
No Tailwind ?inlineTailwind 4's Vite plugin doesn't process @tailwind for ?inline imports.
No client-side hydrationReact Router JS bundles don't load through the proxy.

7. Plus Dev Stores Can't Approve Test Charges

Create a non-Plus (Basic) dev store for billing testing. Plus dev stores show "This feature isn't currently available."

8. Custom Apps Cannot Use Billing API

App must be Public or Unlisted distribution — not Custom.

9. CloudFront Strips Host Header — CSRF Failure

// CDK fix: add x-forwarded-host custom origin header
const apiOrigin = new origins.HttpOrigin(apiDomain, {
  customHeaders: { 'x-forwarded-host': config.domainName },
});

10. shopify app dev --store Flag Ignored with Cached Association

# WRONG — --store flag is ignored
shopify app dev --store different-store.myshopify.com

# CORRECT — reset cached association first
shopify app dev --reset

Quick Reference

shopifyApp() Config Skeleton

const shopify = shopifyApp({
  apiKey: process.env.SHOPIFY_API_KEY,
  apiSecretKey: process.env.SHOPIFY_API_SECRET || '',
  apiVersion: ApiVersion.October25,
  scopes: process.env.SCOPES?.split(','),
  appUrl: process.env.SHOPIFY_APP_URL || '',
  authPathPrefix: '/auth',
  sessionStorage: new DynamoDBSessionStorage(),
  distribution: AppDistribution.AppStore,
  billing: { /* plan definitions */ },
  future: { expiringOfflineAccessTokens: true },
});

Route Naming Conventions

PatternPurposeURL
app.tsxLayout (nav, Polaris, billing)—
app._index.tsxDashboard/app
app.forms.$id.tsxForm editor/app/forms/:id
proxy.tsxPublic proxy layout—
api.submit-application.tsxPOST endpoint/api/submit-application
webhooks.app.uninstalled.tsxWebhook handler—

Prefixes: app. = authenticated admin, proxy. = public HMAC-verified, api. = headless, webhooks. = Shopify webhooks.

Layout Route (app.tsx) Pattern

  • Loads Polaris styles, runs authenticate.admin(), syncs billing to DynamoDB
  • Provides <AppProvider>, <s-app-nav>, <ClientOnly> wrapping <PolarisProvider>
  • Includes ErrorBoundary and headers from boundary
  • Uses shouldRevalidate to skip revalidation on POST (prevents flicker)

shouldRevalidate Pattern

export const shouldRevalidate = ({ formMethod, defaultShouldRevalidate }) => {
  if (formMethod === 'POST') return false;
  return defaultShouldRevalidate;
};

ClientOnly Component

export function ClientOnly({ children, fallback = null }: Props) {
  const [mounted, setMounted] = useState(false);
  useEffect(() => { setMounted(true); }, []);
  return mounted ? <>{children}</> : <>{fallback}</>;
}

SSR Hydration Rule

NEVER use inline event handlers — they cause hydration mismatches in SSR apps. Always use onClick, onChange, etc. via React state and handlers.


Project Structure

your-app/
  app/
    routes/              # File-based routes (app.*, proxy.*, api.*, webhooks.*)
    components/          # React components (ClientOnly, etc.)
    services/            # Business logic (*.server.ts)
      dynamodb/          # keys.ts, mappers.ts
    lib/                 # Core infra (dynamodb.server.ts, config.server.ts)
    lambda-workers/      # SQS handlers (email, export)
    shopify.server.ts    # shopifyApp() config
    lambda.server.ts     # Lambda entry point
  infra/                 # CDK stacks
  tests/                 # Playwright E2E
  extensions/            # Shopify app extensions

Service Conventions

  • All DB access through app/services/*.server.ts
  • First param is always shopDomain (multi-tenant scoping)
  • Keys via app/services/dynamodb/keys.ts
  • .server.ts suffix = tree-shaken from client bundles

Key Environment Variables

VariableSourceDescription
SHOPIFY_API_KEYSecrets ManagerApp API key
SHOPIFY_API_SECRETSecrets ManagerApp API secret
SHOPIFY_APP_URLSSMApp URL
DYNAMODB_TABLESSMTable name
DYNAMODB_ENDPOINT.env (local)DynamoDB Local endpoint
AWS_S3_BUCKETSSMDocuments bucket
EMAIL_QUEUE_URLSSMSQS email queue URL

Scope

In scope: Embedded Shopify apps, React Router v7, DynamoDB single-table, Lambda, SQS, CDK, Polaris, App Proxy, Billing API, Webhooks, GDPR compliance.

Out of scope: Liquid theme development, Hydrogen/headless, Checkout UI Extensions, Shopify Functions, POS extensions, Prisma/PostgreSQL.


Companion Tools

Use the official Shopify Dev MCP server alongside this skill for documentation search, GraphQL schema exploration, and Liquid validation. See shopify.dev/docs/apps/build/devmcp.

MCP config (add to your .claude/settings.json or Claude Desktop config):

{
  "mcpServers": {
    "shopify-dev-mcp": {
      "command": "npx",
      "args": ["-y", "@anthropic-ai/shopify-dev-mcp"]
    }
  }
}

Orchestrator Pattern

When building a new feature, decompose into layers and build each before moving to the next:

  1. Explore — constraints, architecture, security, integration points (references/feature-exploration.md)
  2. Database — keys, entity design, queries (references/dynamodb-patterns.md)
  3. API — service functions, route loader/action (references/react-router-patterns.md)
  4. UI — Polaris components, state management (references/polaris-ui-patterns.md)

Step 0 runs when planning or analyzing a feature — not during active implementation. Build and test each layer independently. This prevents coupling bugs and makes code review easier.


When to Consult References

TaskReference
Routing, loaders, actions, fetchersreferences/react-router-patterns.md
DynamoDB keys, queries, transactionsreferences/dynamodb-patterns.md
GraphQL API, customers, metafieldsreferences/shopify-api-patterns.md
Billing flow, plans, trialsreferences/billing-patterns.md
App Proxy pages, HMAC, formsreferences/app-proxy-patterns.md
Webhook handlers, GDPR, uninstallreferences/webhook-patterns.md
Lambda cold start, SQS workersreferences/lambda-architecture.md
CDK stacks, CloudFront, monitoringreferences/cdk-infrastructure.md
Polaris UI, design tokens, compositionreferences/polaris-ui-patterns.md
Email queuing, suppression, Resendreferences/email-patterns.md
Multi-tenancy, CORS, S3 presigned URLsreferences/security-patterns.md
Playwright, FrameLocator, selectorsreferences/testing-patterns.md
Docker, DynamoDB Local, startupreferences/local-dev-patterns.md
Zero-to-production deployment, AWS setup, DNS, CI/CDreferences/production-deployment.md
Planning a new feature, analyzing constraintsreferences/feature-exploration.md
File naming, service patterns, env vars, TOMLreferences/project-conventions.md

Keep looking

Skills are one crate of 628,853. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.