Quick Start
Run the store with zero configuration, then connect sign-in and a real database.
Two stages. The first takes a minute and needs no accounts; the second connects sign-in and a real database.
Run it with zero configuration
git clone <this-repo>
cd ecommerce-boilerplate
npm install
npm run devOpen http://localhost:3000. With no .env.local, the store runs on the demo backend: a bundled 18-product catalog with working filters, search, product pages, cart, and a local checkout that confirms without charging anything. Writes that need a database (admin CRUD, profiles, quote requests) are rejected with a message pointing here.
That is enough to evaluate the storefront, read the code, and start restyling. Everything below turns it into a real store.
Prerequisites
- Node.js 20 or newer. The
firebase-adminv14 SDK used by the seed script and the optional Stripe webhook requires Node 20+. - npm (ships with Node).
- A Google account to create a free Firebase project — needed for the default sign-in path, not for the Auth.js one (authentication).
- Nothing else for the default data path: SQLite is a file the project creates for you. A PostgreSQL database only if you pick that instead — local, a container, Neon, Supabase, RDS, anything with a
postgresql://string.
Two decisions, not one
Sign-in and storage are separate choices, and this is the one thing worth reading before you start:
| What it does | What you set | |
|---|---|---|
| Identity | Sign-in, saved carts, order history, admin access | Firebase Auth by default, or Auth.js (AUTH_PROVIDER=authjs) with sessions stored in your own SQL database. |
| Data | Products, carts, quotes, orders, discounts, customers | Your choice: SQLite (a file, nothing to install), Postgres via Prisma (the same adapter, for production), Supabase, or Firestore. |
The steps below take the Firebase Auth path, because it is the default and works against every data backend. Then pick where the data lives: SQLite to get real writes immediately with nothing to install, Postgres for the same adapter against a server you can deploy, or Firestore if you would rather run no database at all and get realtime carts for free. Everything after that step is identical — the app calls one interface and never knows the difference.
Firestore-as-data stays paired with Firebase Auth. The shipped firestore.rules gate on request.auth, so a browser holding an Auth.js session cookie is a stranger to Firestore and every client-side read is denied. Dropping Firebase entirely means running SQLite, Postgres, or Supabase — see authentication.
Steps
Clone and install
git clone <this-repo>
cd ecommerce-boilerplate
npm installnpm install runs a postinstall step (fumadocs-mdx) that generates the type definitions for these docs — that is expected.
Create a Firebase project (identity)
- Create a project at console.firebase.google.com.
- Add a Web app and copy its config values (you will paste them into
.env.localnext). - Enable Authentication → sign-in methods: Google and Email link (passwordless).
- Enable Cloud Firestore — required only if Firestore will also hold your data.
Skipping this step is possible on a SQL backend: set AUTH_PROVIDER=authjs and sign-in runs on Auth.js with session rows in your own database, no Firebase project at all. See authentication for that path; the rest of this page assumes the Firebase default.
Configure environment variables
Copy the example file and fill in your Firebase web-app values:
cp .env.example .env.localThe variables that switch sign-in on are the Firebase NEXT_PUBLIC_* values plus the email sign-in redirect URL:
| Variable | Value |
|---|---|
NEXT_PUBLIC_FIREBASE_API_KEY | From your Firebase web-app config |
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN | e.g. your-project.firebaseapp.com |
NEXT_PUBLIC_FIREBASE_PROJECT_ID | e.g. your-project |
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET | e.g. your-project.appspot.com |
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID | From your Firebase web-app config |
NEXT_PUBLIC_FIREBASE_APP_ID | From your Firebase web-app config |
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID | Optional (Analytics) |
NEXT_PUBLIC_EMAIL_SIGNIN_REDIRECT_URL | http://localhost:3000/complete-login in development |
Set the redirect URL for local development:
NEXT_PUBLIC_EMAIL_SIGNIN_REDIRECT_URL=http://localhost:3000/complete-loginThe passwordless email-link sign-in returns the visitor to this absolute URL to finish signing in. In production, set it to your live URL (e.g. https://yourstore.com/complete-login) and make sure that domain is listed under Firebase Auth → Settings → Authorized domains.
Firebase web credentials are safe to expose to the browser. The NEXT_PUBLIC_* keys are designed to be public — access control is enforced by the shipped firestore.rules, not by hiding the keys. See the environment variables reference for the full list, including the optional Stripe, Resend, and SQL-backend variables.
Choose where the data lives
A real database in a file — nothing to install, nothing to connect to, and every write the app can do actually persists:
DATABASE_URL="file:./dev.db"
COMMERCE_BACKEND=sqlite
NEXT_PUBLIC_COMMERCE_BACKEND=sqliteCOMMERCE_BACKEND=sqlite npm run prisma:generate
COMMERCE_BACKEND=sqlite npm run prisma:pushCOMMERCE_BACKEND picks the schema file the Prisma CLI loads, so pass it on the commands too — not just in .env.local.
It is the same adapter Postgres uses, so switching later is two env vars. Delete dev.db to start over. Details and limits (not for serverless): SQLite backend.
Supabase is the fourth option — hosted Postgres behind the same adapter family. See choosing a backend for the full comparison.
Run the dev server
npm run devOpen http://localhost:3000. The storefront renders immediately — the landing page, header, and navigation all work right away. Product grids stay empty until you add catalog data, either by seeding demo data (next step) or by creating products in the admin dashboard.
(Optional) Seed demo data
The repo ships a seed script that fills whichever backend you just chose with 18 demo products (3 per category, matching the slugs in src/config/site.ts) and features 6 of them on the home page. Two of the products carry variants (a size axis and a color axis with a price override) so the variant UI is demoable. They are the same fixture the demo backend serves, so a seeded store shows exactly what the zero-config one did.
First, preview the plan — this needs no credentials and writes nothing:
npm run seed -- --dry-runThen run it. The script reads .env.local, so if COMMERCE_BACKEND and the credentials are already there, this is the whole command:
npm run seedNothing extra: the script writes through the same server adapter the app uses, with the same DATABASE_URL (or SUPABASE_*) credentials. Push the schema first if you have not.
COMMERCE_BACKEND=sqlite npm run seed # or postgres / supabaseThe script is idempotent on every backend: products are written by their catalog id and overwritten in place, and stale featured copies are removed before the new ones are added, so re-running never duplicates anything.
No service account yet? Skip this step and create products from the admin dashboard instead — you just need admin access first (next step).
Grant yourself admin access
The admin dashboard lives at /admin and is gated by a role — being signed in is not enough. The role is a record keyed by the uid your identity provider issued, stored in whichever backend holds your data:
- Sign in to the store once at http://localhost:3000/login so your user exists.
- Add the record for your uid (shown on the
/adminaccess-denied screen, or in Firebase Auth → Users):- SQLite:
sqlite3 dev.db "insert or ignore into admins (uid) values ('<your-uid>');" - Postgres / Supabase:
insert into admins (uid) values ('<your-uid>') on conflict (uid) do nothing; - Firestore: create a document in the
adminscollection whose document id is your uid. The body can be empty.
- SQLite:
- Reload — the Admin entry appears in the account menu, and
/adminunlocks.
On the SQL backends, grant admin only from a SQL client — the RPC route never exposes a write to admins. On Firestore, the same holds through the rules:
Clients can never write to the admins collection — manage it only from the Firebase console. In firestore.rules, the admins/{uid} match allows read: if isOwner(uid) and write: if false; the isAdmin() helper checks for a document at admins/{request.auth.uid}. The in-app UI check (useIsAdmin) is convenience only — real enforcement lives in the rules, so deploy them before going to production with firebase deploy --only firestore:rules.
What you get out of the box
With sign-in configured, a data backend selected, and a catalog in place, you have a working store:
- Storefront at
/— landing page, catalog with category filtering, and product detail pages. - Cart & quotes — guest carts in
localStorage, signed-in carts persisted to your backend, and a quote-request checkout that records the request as"Pending". - Account area at
/account— profile, addresses, and quote/order history. - Admin dashboard at
/admin— product CRUD, featured curation, the quote pipeline, order fulfillment, customers, and analytics.
Stripe payments and Resend email notifications stay dormant until you set their env vars — the store works fully without either.