Deployment
Deploy to Vercel with the right environment variables and production settings.
The boilerplate works out of the box on Vercel: import the repo, set the environment variables from .env.example, and deploy. Any Node host that supports Next.js 14 works too. The store is quote-based by default — Stripe payments, Resend emails, and the SQL backends are all optional and env-gated, so a first deploy needs nothing more than your Firebase web credentials and a sign-in redirect URL.
A default (Firebase) build succeeds with no credentials at all: src/lib/firebase/config.ts falls back to demo values so the SDK initializes during prerender. You still need real env vars at runtime for the deployed store to talk to your own Firebase project.
Deploy to Vercel
Import the repository
In the Vercel dashboard, Add New → Project and import your fork/clone. Vercel auto-detects Next.js 14 (App Router) — the default build command next build and output settings need no changes.
Set the environment variables
Open Project Settings → Environment Variables and add every value your deployment needs from .env.example. For a default quote-based store, the Firebase web credentials plus the sign-in redirect URL are the only required entries:
| Variable | Required | Notes |
|---|---|---|
NEXT_PUBLIC_FIREBASE_API_KEY | Yes | Firebase web app config |
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN | Yes | e.g. your-project.firebaseapp.com |
NEXT_PUBLIC_FIREBASE_PROJECT_ID | Yes | e.g. your-project |
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET | Yes* | Required for product image uploads / media library |
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID | Yes | Firebase web app config |
NEXT_PUBLIC_FIREBASE_APP_ID | Yes | Firebase web app config |
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID | No | Analytics, optional |
NEXT_PUBLIC_EMAIL_SIGNIN_REDIRECT_URL | Yes | Set to your production /complete-login URL (see below) |
NEXT_PUBLIC_* values are compiled into the browser bundle. Firebase web credentials are designed to be public — enforce access control with firestore.rules, not by hiding the keys.
Point the sign-in redirect at production
Passwordless email-link sign-in redirects back to the URL in NEXT_PUBLIC_EMAIL_SIGNIN_REDIRECT_URL. In development this is http://localhost:3000/complete-login; in production set it to your deployed origin plus /complete-login:
NEXT_PUBLIC_EMAIL_SIGNIN_REDIRECT_URL=https://shop.example.com/complete-loginAuthorize the production domain in Firebase
The redirect domain must be listed in Firebase console → Authentication → Settings → Authorized domains. Add your production hostname (e.g. shop.example.com, and any Vercel preview domain you want sign-in to work on) or the email-link flow is rejected.
Add optional integrations (if used)
Only set these when you actually want the feature — leaving them unset keeps the store quote-only:
| Feature | Env vars | Effect when unset |
|---|---|---|
| Stripe Checkout | STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET | Checkout button never appears |
| Order recording from webhook | FIREBASE_SERVICE_ACCOUNT_JSON (or GOOGLE_APPLICATION_CREDENTIALS/FIREBASE_SERVICE_ACCOUNT path) | Webhook acknowledges events but can't write orders |
| Email notifications | RESEND_API_KEY, RESEND_FROM_EMAIL, QUOTE_NOTIFICATION_EMAIL | Emails skipped, store works normally |
| SQL backend | NEXT_PUBLIC_COMMERCE_BACKEND + COMMERCE_BACKEND (+ SUPABASE_* or DATABASE_URL) | Default Firebase backend runs client-side |
Deploy
Trigger the deploy. On success, visit the site, sign in once, and (if you want the admin dashboard) add your user id to the Firestore admins collection.
The Stripe webhook needs a service account
Stripe order recording happens server-side with the Firebase Admin SDK, which needs privileged credentials that the browser never sees. On Vercel the convenient option is FIREBASE_SERVICE_ACCOUNT_JSON — paste the service-account key's JSON contents (not a file path) directly into the environment variable:
Generate a key
Firebase console → Project settings → Service accounts → Generate new private key. This downloads a JSON file. The file is git-ignored — never commit it.
Paste the contents into Vercel
Copy the whole JSON object and paste it as the value of FIREBASE_SERVICE_ACCOUNT_JSON:
FIREBASE_SERVICE_ACCOUNT_JSON={"type":"service_account","project_id":"your-project", ... }Alternatively, provide a key-file path via GOOGLE_APPLICATION_CREDENTIALS or FIREBASE_SERVICE_ACCOUNT — but a pasted JSON blob is simpler on serverless hosts where there is no persistent filesystem.
Register the webhook endpoint
In the Stripe dashboard, add a webhook pointing at https://your-domain/api/webhooks/stripe and copy its signing secret into STRIPE_WEBHOOK_SECRET.
Without admin credentials the POST /api/webhooks/stripe handler still acknowledges events (so Stripe won't retry-storm) but logs that the order could not be recorded. If paid orders aren't showing up in /admin/orders, check that FIREBASE_SERVICE_ACCOUNT_JSON is set on the deployment.
The same admin credentials also power the npm run seed demo-data script — see Demo data. Note the SQL backends require FIREBASE_SERVICE_ACCOUNT* too, because Firebase Auth stays the identity provider and its ID tokens are verified server-side.
Configure images for your CDN
Product images (primaryImageURL) render through next/image, so every host they can be served from must be listed in images.remotePatterns in next.config.mjs. The default config already allows Firebase Storage and the demo photography host:
images: {
remotePatterns: [
{
protocol: "https",
hostname: "firebasestorage.googleapis.com",
},
{
// Editorial/lifestyle photography used by the storefront sections.
protocol: "https",
hostname: "picsum.photos",
},
],
},If you serve product images from your own CDN or storage bucket, add its hostname here before deploying, or next/image will refuse to optimize those URLs. Uploads to Firebase Storage work with no change — firebasestorage.googleapis.com is already allowed.
next.config.mjs also lists @prisma/client, @prisma/adapter-pg, and pg under experimental.serverComponentsExternalPackages. This keeps the optional SQL-backend packages out of the bundle so a default Firebase build succeeds without them installed — you don't need Prisma or Postgres for a standard deploy.
Continuous integration
A ready-made GitHub Actions workflow lives at docs/ci.example.yml. It runs on pushes to main and on every pull request, and executes lint → typecheck → test → build on Node 20:
- name: Lint
run: npm run lint
- name: Typecheck
run: npm run typecheck
- name: Test
run: npm test
- name: Build
run: npm run build
# No env needed: src/lib/firebase/config.ts falls back to demo
# values so the SDK initializes during prerender without credentials.Activate it by copying it into the workflows directory (it ships outside .github/workflows/ because automated pushes can't create workflow files without extra permissions):
mkdir -p .github/workflows && cp docs/ci.example.yml .github/workflows/ci.ymlThe build step needs no environment variables — the Firebase config's demo-value fallback lets the SDK initialize during prerender. And because the optional SQL packages are external, the default build never requires @prisma/client, pg, or a DATABASE_URL.
Production readiness
Before going live, don't ship the demo defaults straight to production:
- Wire a live FX feed. Currency conversion uses demo rates baked into
siteConfig.currencies. Prices are stored in the base currency (USD) and converted at display time — replace the demo rates with a real feed for accurate customer-facing prices. - Deploy and verify
firestore.rules. The shipped rules enforce all access control (admin gating, append-only audit log, public catalog reads). Deploy them withfirebase deploy --only firestore:rulesand confirm they're active — the UIuseIsAdmincheck is convenience only. If you use image uploads, deploystorage.rulestoo (firebase deploy --only storage). - Use a real Resend sender.
onboarding@resend.devis for testing only. SetRESEND_FROM_EMAILto a domain you've verified in Resend before enabling email notifications. - Review the legal pages. The privacy/terms/cookie pages under
src/app/are placeholders — review them with counsel.