Nordcom Commerce is a production-grade, multi-tenant storefront platform that serves many tenants from a single deployment. It pairs Next.js 16 with Shopify as the commerce backend and an embedded Payload CMS for content, and ships an operator dashboard, a marketing site, and a small set of reusable packages — all in one TypeScript monorepo.
- Multi-tenant out of the box. A single Next.js deployment serves arbitrarily many
shops; tenants are resolved by hostname in middleware and routed under a
/[domain]/[locale]/…segment, so adding a new shop is a database row, not a deploy. - Headless commerce. Shopify Storefront API for catalog/cart/checkout, Shopify Admin API for back-office operations, all behind a uniform fetch layer.
- Composable content. Payload CMS blocks and structured documents drive
marketing pages, navigation, and component-level CMS overrides, embedded in the
admin app and shared with the storefront via
@nordcom/commerce-cms. - i18n that respects shops. Locales live on the shop record; fallbacks degrade
from
request → shop default → platform defaultwith recursion guards. - Edge-friendly caching. Per-tenant, per-entity cache tags with surgical Shopify
webhook revalidation and CMS-driven
revalidateTaghooks. - Type-safe end to end. Strict TypeScript,
noUncheckedIndexedAccess, and a typed error hierarchy used uniformly across packages. - One toolchain. Turborepo + pnpm workspaces, Biome for lint/format, Vitest for tests — no ESLint, no Prettier, no surprises.
Prerequisites: Node.js (see
.nvmrc),pnpm, and a running MongoDB instance for the data layer.
# 1. Install dependencies.
pnpm install
# 2. Configure environment variables. See .env.example for the full list.
cp .env.example .env
# Required at minimum: MONGODB_URI, AUTH_SECRET, SERVICE_DOMAIN.
# 3. Build the workspace packages (apps depend on each package's dist/).
pnpm build:packages
# 4. Start everything in parallel.
pnpm devYou should now have:
| App | URL |
|---|---|
| Storefront | http://localhost:1337 |
| Admin | http://localhost:3000 |
| Landing | http://localhost:3001 |
To start only one app, use pnpm dev:storefront, pnpm dev:admin, or pnpm dev:landing.
| Package | Path | Description |
|---|---|---|
@nordcom/commerce-storefront |
apps/storefront |
Public, multi-tenant storefront for end customers. |
@nordcom/commerce-admin |
apps/admin |
Operator dashboard for managing shops and integrations. |
@nordcom/commerce-landing |
apps/landing |
Marketing & documentation site. |
| Package | Path | Description |
|---|---|---|
@nordcom/commerce-db |
packages/db |
Mongoose models + service layer for shops, users, sessions, identities. |
@nordcom/commerce-errors |
packages/errors |
Typed error classes with stable codes for API/UI/SDK consumers. |
@nordcom/commerce-shopify-graphql |
packages/shopify-graphql |
Apollo transform that injects Shopify @inContext(country, language). |
@nordcom/commerce-shopify-html |
packages/shopify-html |
Convert Shopify rich text HTML to React trees or plain text. |
@nordcom/commerce-marketing-common |
packages/marketing-common |
Shared Nordstar theme and primitives for marketing surfaces. |
| Concern | Tool |
|---|---|
| Package manager | pnpm 11.x (workspaces) |
| Runtime | Node.js (see .nvmrc) |
| Build / cache | Turborepo 2.x (with optional Remote Cache) |
| Framework | Next.js 16, React 19 |
| Lint / format | Biome 2.x — no ESLint / Prettier |
| Testing | Vitest 4.x + Playwright for E2E |
| Bundling (libs) | Vite (per-package dist/) |
| Data | MongoDB (Mongoose 9.x) |
| Auth | NextAuth v5 (@auth/core) |
All scripts go through dotenv -c -- turbo …, so .env / .env.local are loaded
automatically. Run from the repo root unless noted.
pnpm dev # All apps in parallel
pnpm dev:storefront # Only the storefront (http://localhost:1337)
pnpm dev:admin # Only the admin (http://localhost:3000)
pnpm dev:landing # Only the marketing site (http://localhost:3001)pnpm build # Build everything (Turbo-cached)
pnpm build:packages # Only ./packages/* — required before lint/typecheck/test
pnpm build:admin # Filter to the admin apppnpm lint # biome lint .
pnpm typecheck # turbo run typecheck (each app does `tsc -noEmit`)
pnpm format:check # biome check --write --unsafe . (auto-fixes!)
pnpm format # biome lint --write + biome format --writepnpm test # Vitest, all projects, with coverage
pnpm test:watch # Watch mode
pnpm dotenv -c -- vitest run path/to/file.test.ts # Single file
pnpm dotenv -c -- vitest run -t "describe or it name" # By name
pnpm dotenv -c -- vitest run --project @nordcom/commerce-storefront # One project
pnpm test:e2e # Playwright (admin, storefront)Tests need
MONGODB_URIpointing at a real database —@nordcom/commerce-dbconnects at module load.
pnpm clean # rm dist / .next / .turbo / coverage everywhereCopy .env.example to .env and fill in the values you need. See .env.example for the full, commented list.
- A request arrives at the Next.js middleware (
apps/storefront/src/proxy.ts). - The middleware normalizes the
hostheader (stripping ports,.localhost, and Vercel preview suffixes) and callsShop.findByDomain(hostname)against MongoDB. - On a hit, it rewrites the URL into the
/[domain]/[locale]/…segment so the App Router serves the page in the tenant's context. - On
NotFoundError, the middleware rewrites toSERVICE_DOMAIN/status/unknown-shop/. - Every Shopify call is built through
ShopifyApolloApiClient({ shop, locale }), so tenant context is never implicit.
To add a tenant, insert a row into the shops collection — no redeploy required.
Pull requests are welcome. Before opening one:
- Make sure
pnpm installsucceeds with the pinnedpackageManager(pnpm 11.x). - Run
pnpm build:packagesbefore linting, typechecking, or testing in a fresh checkout — apps depend on each package'sdist/. - Run
pnpm lint,pnpm typecheck, andpnpm testlocally; CI runs the same. - Follow the Biome formatting (4-space indent, single quotes, semicolons,
trailing commas,
lineWidth: 120).pnpm format:checkwill rewrite mismatches. - Use
import typefor type-only imports —useImportTypeis enforced as an error. - Plain
console.logwill fail lint; onlywarn/error/info/debugare permitted outside of test/config files.
- 2019–2026 — Filiph Sandström, @filiphsps
- 2023–2024 — Nordcom Group Inc., @NordcomInc
- 2024 — Nordcom AB, @NordcomInc
This repository is private. Copyright notices:
- © 2019–2026 Filiph Sandström.
- © 2023 Nordcom Group Inc.
- © 2024 Nordcom AB.