Skip to content
Open-source e-commerce boilerplate

Fork the repo. Ship a store.

A production storefront, admin dashboard, and Stripe checkout in one repo. Swap the backend, keep the app.

0env vars to boot
The bundled demo catalog serves the store until you connect one.
4backends, one interface
Demo, Firebase, Supabase, Postgres. Adapters, not forks.
18demo products
Filters, search, variants, and stock, already populated.
2languages, 4 currencies
Resolved on the server, so the first paint is already right.
What is in the box

A whole store, not a landing page with a cart.

Every screen below is this repository, on a seeded database, with no credentials configured — the same build CI produces on each push.

The storefront landing page: display headline beside a product photograph
An editorial hero over real photography, then the catalog. Brand colour, language and currency all switch at runtime.
Backends

One commerce interface. Swap the backend.

Every storefront and admin call goes through a typed commerce module. Change the adapter, keep the app.

.env.local

NEXT_PUBLIC_COMMERCE_BACKEND=firebase
Implemented in
src/lib/commerce/firebase.ts
Credentials
NEXT_PUBLIC_FIREBASE_*

Zero-config default. Firestore rules ship in the repo.

Satisfy this, and the app workssrc/lib/commerce/types.ts

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>;
}
Where a call goes

One call site. Four places it can land.

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.

Features

Everything a real store needs, already wired.

Studio Headphones X1

Northsound

Studio Headphones X1

AUD-001

$129.99

Audio

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.

What holds it together

The parts that are easy to get wrong, already right.

A starter is only worth forking if its foundations survive contact with production. These are the ones that usually do not.

  • Checkout cannot oversell

    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.

  • Admin writes are audited

    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.

  • Prices come from the catalog

    api/checkout/route.ts

    The checkout route re-reads every product and variant server-side. Nothing the browser sends can move an amount.

  • Every push runs the gate

    .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.

From clone to store

Three commands. No account, no database, no keys.

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.

  1. Clone and install

    $ git clone github.com/nebulanollie/ecommerce-boilerplate$ npm install

    One dependency tree and no native toolchain to satisfy. The postinstall step only builds the documentation index.

  2. Start it

    $ npm run dev

    The bundled demo catalog answers every read: 18 products, category filters, search, variants, a cart, and a checkout that confirms locally.

  3. Decide where the data lives

    $ echo "NEXT_PUBLIC_COMMERCE_BACKEND=sqlite" >> .env.local

    One 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 start
Before you fork it

The questions worth asking first.

Including the answers that are not flattering.

Is this production-ready, or a demo?

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.

Do I have to use Firebase?

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.

What if my backend is not one of the four?

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.

What is deliberately not here?

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.

How do I take updates after I have changed things?

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.

What is the licence?

MIT. Commercial use, closed-source forks, and rebranding are all fine, with no attribution required in your product. Attribution is appreciated, not demanded.

Open source. Self-hostable. Yours to fork.

git clone github.com/nebulanollie/ecommerce-boilerplate

Runs with no configuration. Connect a backend when you want real data.