Skip to content

Deployment

Where each BabyMart app is hosted, how to configure it for production, and the checklist to run before going live.

Every web app deploys from the same repository. The storefront, admin dashboard and API each run as their own Vercel project. MongoDB Atlas holds the data, and the docs are a plain static site that any host can serve.

flowchart LR
  subgraph Vercel
    W["Storefront<br/>apps/web"]
    A["Admin<br/>apps/admin"]
    API["API<br/>apps/api serverless"]
  end
  W -->|NEXT_PUBLIC_API_URL| API
  A -->|VITE_NEXT_PUBLIC_API_URL| API
  M[Mobile app] --> API
  API --> DB[(MongoDB Atlas)]
  ST[Stripe] -->|webhooks| W
  ST -->|webhooks| API
  D["Docs<br/>apps/docs/dist"] -.static.- CDN[Any static host]
  1. In Vercel, import the repository and set Root Directory to apps/web. The root vercel.json sets the install command to pnpm install, so the workspace packages are linked.
  2. Add the storefront’s variables from Environment variables. Point NEXT_PUBLIC_API_URL and API_ENDPOINT at the production API, and set NEXT_PUBLIC_SITE_URL to the storefront’s own domain.
  3. Deploy. After that, .github/workflows/deploy.yml redeploys on every push to main that touches apps/web, once the VERCEL_ORG_ID, VERCEL_PROJECT_ID and VERCEL_TOKEN repository secrets are set.

apps/admin/vercel.json already sets everything up. It installs from the monorepo root, runs pnpm turbo run build --filter=@babyshop/admin, serves dist, rewrites every path to index.html (so React Router can handle deep links), and adds security headers. Create a Vercel project with Root Directory apps/admin, then add VITE_NEXT_PUBLIC_API_URL and the VITE_FIREBASE_* keys.

apps/api/vercel.json builds server.ts with @vercel/node and sends every path to it. server.ts exports the Express app, and when NODE_ENV=production it skips app.listen(), so Vercel calls the exported app as a serverless function.

  1. Create a Vercel project with Root Directory apps/api.
  2. Set NODE_ENV=production, MONGO_URI, a JWT_SECRET of at least 32 characters, CLIENT_URL (the storefront domain) and ADMIN_URL (the admin domain). The last two make up the CORS allowlist.
  3. Add the Stripe, SSLCommerz, Cloudinary/S3, SMTP and OAuth variables you use, and set SERVER_URL to the API’s public URL so SSLCommerz callbacks reach it.

Create a cluster and a database user, and copy the connection string into MONGO_URI. Vercel functions don’t have fixed IP addresses, so the Atlas network access list must allow 0.0.0.0/0. Make up for that with a strong, unique database password. Seed the catalogue with Demo data if you want a starting point.

  1. Switch the dashboard to live mode and use live keys everywhere: the publishable key in the storefront and mobile app, and the secret key in the storefront and the API.
  2. Add two webhook endpoints, one per receiver, and copy each signing secret into that app’s STRIPE_WEBHOOK_SECRET:
    • https://<storefront>/api/stripe-webhook: checkout.session.completed, checkout.session.async_payment_succeeded
    • https://<api>/api/payments/webhook: checkout.session.completed, payment_intent.succeeded
  3. Place one real order and check that it becomes paid.

The payment flow is explained on Payments.

Terminal window
pnpm --filter @babyshop/docs build

This writes a static site to apps/docs/dist/. Serve it from Vercel (Root Directory apps/docs, output dist), Netlify, Cloudflare Pages or GitHub Pages. Set site in apps/docs/astro.config.mjs to the public URL to turn on the sitemap.

Before a store build, change the release API URL in apps/mobile/src/config/environment.ts, the Stripe key in App.tsx and the Google client IDs in src/services/googleAuthService.ts. Also replace the iOS bundle identifier: it is still the template value org.reactjs.native.example.*, while Android uses com.babymart.app. See Mobile app.

  • JWT_SECRET is a long random string (32+ characters, enforced in production).
  • Every API URL points at production, not localhost: NEXT_PUBLIC_API_URL, API_ENDPOINT, VITE_NEXT_PUBLIC_API_URL, and the mobile release URL.
  • CLIENT_URL and ADMIN_URL on the API match the real domains (CORS).
  • Stripe live keys and live webhook secrets are set, and a real order reached paid.
  • SSLCOMMERZ_IS_LIVE=true if you take SSLCommerz payments.
  • Firebase → Authentication → Authorized domains includes the storefront and admin domains.
  • Demo and read-only accounts (preview@gmail.com, demo@gmail.com, test@gmail.com) are removed or have new passwords.
  • Any .env file that was ever committed is removed from git, and every secret in it is rotated.
  • Images are re-uploaded to your own Cloudinary account (pnpm reimage), and demo photos are replaced with ones you have the rights to.
  • apps/web/public/robots.txt and the sitemap use your domain. next-sitemap.config.js holds the site URL.
  • The admin CSP connect-src includes your API’s origin.