BlogHow to Structure a Shopify App pnpm Mono...
short-formhiringautomation

How to Structure a Shopify App pnpm Monorepo with Remix and Vite

July 27, 2025

The Monorepo Advantage for Shopify Apps

If you are building a single, isolated Shopify app, the standard npm create @shopify/app@latest boilerplate is sufficient. But if you are an agency or an independent developer building a suite of 3-4 interconnected Shopify applications that share billing logic, authentication flows, and Prisma database schemas, relying on separate repositories creates an unmaintainable sprawl of duplicated code.

A pnpm monorepo architecture solves this by allowing multiple independent Shopify Remix apps to live in the same repository while sharing core utility packages.

Monorepo Structure

The architecture relies heavily on pnpm workspaces. Your root directory should look like this:

/my-shopify-suite
├── pnpm-workspace.yaml
├── packages/
│   ├── database/       # Shared Prisma schema and Neon client
│   ├── billing/        # Shared Stripe/Shopify billing logic
│   └── ui-components/  # Shared React components (Polaris wrappers)
└── apps/
    ├── order-manager/  # Remix App 1
    ├── inventory-sync/ # Remix App 2
    └── cx-portal/      # Remix App 3

The pnpm-workspace.yaml file is the linchpin. It explicitly tells pnpm which directories contain packages and applications:

packages:
  - 'apps/*'
  - 'packages/*'

Shared Packages: Database and Prisma

The most critical shared package is the database. When multiple apps need to verify a shop's subscription status, they shouldn't each maintain their own database connection logic.

Inside packages/database, we define our schema.prisma and export a typed Prisma client connected to a Neon Serverless PostgreSQL instance.

In the package.json of the order-manager app, we import this local package using the workspace:* protocol:

"dependencies": {
  "@my-suite/database": "workspace:*"
}

This allows the Remix loaders in the order-manager app to query the shared database flawlessly without duplicating models.

Remix Routing and Shopify OAuth

Shopify embedded apps rely heavily on complex OAuth flows and Webhook handling. In a Remix context, this is handled via standard file-based routing combined with the @shopify/shopify-app-remix package.

Each app in the apps/ directory maintains its own shopify.server.ts configuration. This is necessary because each app has its own distinct Shopify Client ID and API Secret.

When configuring the Vite dev server (vite.config.ts) inside each app, you must ensure that each app runs on a distinct local port (e.g., 3000, 3001, 3002).

Deployment Strategy

Deploying a monorepo requires a CI/CD pipeline that understands workspaces.

If you are using Netlify or Vercel, you configure separate projects for each app. The crucial step is setting the Root Directory or Base Directory in the Netlify UI to apps/order-manager while keeping the repository root at the top level.

This ensures that when Netlify runs pnpm install, it resolves the local workspace dependencies (packages/database), but only builds and deploys the specific Remix app mapped to that subdomain (e.g., orders.my-suite.com).