
Northsound
Studio Headphones X1
AUD-001
$129.99
A production storefront, admin dashboard, and Stripe checkout in one repo. Swap the backend, keep the app.
Every screen below is this repository, on a seeded database, with no credentials configured — the same build CI produces on each push.

Every storefront and admin call goes through a typed commerce module. Change the adapter, keep the app.
.env.local
NEXT_PUBLIC_COMMERCE_BACKEND=firebaseZero-config default. Firestore rules ship in the repo.
Seven services, one object. Every call site in the storefront and the admin goes through it, which is why an adapter is a new file rather than a fork.
export interface CommerceServices {
products: ProductService;
carts: CartService;
quotes: QuoteService;
orders: OrderService;
discounts: DiscountService;
customers: CustomerService;
admin: AdminService;
}
// one of the seven, in full:
interface ProductService {
list(pageSize?: number, cursor?: PageCursor | null): Promise<ProductPage>;
getById(id: string): Promise<Product | null>;
search(term: string, maxResults?: number): Promise<Product[]>;
create(product: Product): Promise<void>;
update(id: string, data: Partial<Product>): Promise<void>;
delete(id: string): Promise<void>;
listFeatured(): Promise<Product[]>;
feature(product: Product): Promise<void>;
unfeature(featuredId: string): Promise<void>;
}The same line of storefront code reaches a bundled fixture, Firestore, or a SQL database. What changes is which adapter answers, and whether the answer comes from the browser or the server.
Any storefront or admin call
await commerce.products.list()One typed interface — CommerceServices
In the browser
Demounset
src/lib/commerce/demo
Bundled catalog. No network, no credentials.
Firebasefirebase
src/lib/commerce/firebase.ts
Reads direct from the browser; rules enforce access.
On the server
POST /api/commerce/[service]/[method]
Verifies the ID token, checks admin status, records the audit entry
Supabase / Prismasupabase · postgres
src/lib/commerce/server/*.ts
Privileged credentials never leave the server.
Admin writesany backend
src/lib/commerce/server/firebase-admin.ts
Admin writes, wherever the data lives.
Admin dashboard
Product CRUD, media, orders, and a quote pipeline.
Quotes and Stripe checkout
Guest or signed-in, with signature-verified webhooks.
Multi-language and multi-currency
Locale routing and live currency switching out of the box.
Inventory and roles
Stock counts, out-of-stock states, role-based admin access.
A starter is only worth forking if its foundations survive contact with production. These are the ones that usually do not.
api/webhooks/stripe/route.ts
The Stripe webhook writes the order and every stock decrement in one transaction, keyed by the session id. A retried delivery hits the same document; two concurrent ones cannot both spend the last unit.
lib/commerce/server/audit.ts
Privileged mutations cross the server, which re-checks admin status and records who changed what with its own clock. An admin cannot skip or backdate the entry.
api/checkout/route.ts
The checkout route re-reads every product and variant server-side. Nothing the browser sends can move an amount.
.github/workflows/ci.yml
Lint, types, the test suite, and a production build with no credentials at all. The demo backend is what makes that last one possible.
Node 20.9 and a terminal is the whole prerequisite list. There is no Docker image to pull and nothing to provision before the store comes up.
$ git clone github.com/nebulanollie/ecommerce-boilerplate$ npm installOne dependency tree and no native toolchain to satisfy. The postinstall step only builds the documentation index.
$ npm run devThe bundled demo catalog answers every read: 18 products, category filters, search, variants, a cart, and a checkout that confirms locally.
$ echo "NEXT_PUBLIC_COMMERCE_BACKEND=sqlite" >> .env.localOne environment variable. SQLite gives you real writes against a file with nothing to install; Postgres, Supabase and Firebase take a connection string or a project.
The same build runs in CI with no credentials set at all, which is what keeps this path from rotting.
Read the quick startIncluding the answers that are not flattering.
The parts listed under Guarantees are production-shaped and covered by tests: the money paths, the access-control matrix, the audit trail, and the SQL adapter against a real database. What it is not is a hosted product — you own the deployment, the payment account, the legal copy, and the decision about what your store needs that this does not have.
No. Data lives wherever COMMERCE_BACKEND points, and identity is a separate switch: AUTH_PROVIDER=authjs runs Auth.js with sessions in your own SQL database, so a Postgres or SQLite deployment needs no Firebase project at all. The one pairing that is fixed is Firestore-as-data, because the shipped firestore.rules gate on request.auth.
Write one file that satisfies CommerceServices and select it with an env var. Nothing in the storefront or the admin imports a vendor SDK — that is the rule the whole seam exists to enforce, and TypeScript fails the build the moment an adapter is missing a method.
Shipping rates, tax calculation, subscriptions and multi-vendor marketplaces. Each is a product decision with no neutral default, and a wrong guess baked into a starter costs more to remove than to add. Per-currency price lists and static rendering of the currency switch are open work, tracked in ROADMAP.md.
The same way you would with any starter: keep this repository as a second remote and merge what you want. There is no update channel and no framework wrapper to upgrade — the code is yours the moment you clone it, which is the trade a boilerplate makes.
MIT. Commercial use, closed-source forks, and rebranding are all fine, with no attribution required in your product. Attribution is appreciated, not demanded.
git clone github.com/nebulanollie/ecommerce-boilerplateRuns with no configuration. Connect a backend when you want real data.