Deployment & Tech Setup Guide
How to take both sites from "works on my machine" to "live on the internet under your own domain."
What you're deploying
Two separate Next.js 15 apps, each configured for static export (output: 'export' in next.config.ts):
apps/mixed-greens— the demo client siteapps/agency— your sales site
Both produce a static out/ directory that any static host can serve. No server, no database, no API needed. This means free hosting forever and zero maintenance surprises.
Recommended host: Cloudflare Pages
Why:
- Free tier covers unlimited sites and hundreds of GB of bandwidth
- Global CDN — sub-200ms loads worldwide
- Automatic HTTPS / SSL
- Git-connected (push to deploy)
- Supports custom domains for free
Alternatives:
- Vercel — also free, also great. Use this if you prefer their UI or want to upgrade to ISR later.
- Netlify — fine. Slightly less generous free tier.
- GitHub Pages — free but slower, fewer features. Not recommended.
The instructions below are for Cloudflare Pages. The Vercel flow is similar — push to GitHub, import, deploy.
Step-by-step: deploy mixed-greens to Cloudflare Pages
1. Push to GitHub
If you haven't yet:
# from the website-local-business root
git init
git add .
git commit -m "Initial: web design outreach kit"
git branch -M main
gh repo create web-design-outreach --private --source=. --push
(If gh CLI isn't installed, do it via the GitHub website — create an empty repo, follow the "push existing repository" instructions.)
2. Connect Cloudflare Pages
- Go to https://dash.cloudflare.com/ → Workers & Pages → Create application → Pages → Connect to Git.
- Select your
web-design-outreachrepo. - Project name:
mixed-greens(this becomesmixed-greens.pages.dev). - Production branch:
main.
3. Build settings (this is the part that trips people up)
Because we have a monorepo (apps/mixed-greens, apps/agency), set:
| Field | Value |
|---|---|
| Framework preset | Next.js (Static HTML Export) |
| Build command | cd apps/mixed-greens && npm install --legacy-peer-deps && npm run build |
| Build output directory | apps/mixed-greens/out |
| Root directory (advanced) | leave blank |
| Environment variable | NODE_VERSION = 20 (or 22) |
Click Save and Deploy. First build takes 2–4 minutes.
4. Repeat for the agency site
Create a second Cloudflare Pages project:
| Field | Value |
|---|---|
| Project name | agency (becomes agency.pages.dev) |
| Build command | cd apps/agency && npm install --legacy-peer-deps && npm run build |
| Build output directory | apps/agency/out |
| NODE_VERSION | 20 or 22 |
Both sites are now live at *.pages.dev URLs. They will redeploy automatically every time you push to main.
Custom domains
Buy the domains
- Use Cloudflare Registrar (at-cost, no markup, ~$10/year for a
.com). - Or your existing registrar (GoDaddy, Namecheap) if you've got a deal.
Point them at Cloudflare Pages
In Cloudflare Pages → your project → Custom domains → Set up a custom domain → enter your domain. CF walks you through the DNS records.
If your domain is already in Cloudflare, it's literally one click. If it's at GoDaddy/Namecheap, you'll add CNAME records pointing to <project>.pages.dev.
Suggested domains:
- For the demo: register the actual restaurant's preferred domain (e.g.,
mixedgreensut.com) only after they sign, and register it under their account or transfer to them. Don't squat domains. - For the agency: pick something short and memorable. Examples:
pinecone.web,firstpage.studio,groundedweb.co. Avoid keyword-stuffed ones likebestlocalwebsites.com— they look spammy and hurt outreach trust.
Environment variables (per site)
Neither site needs env vars by default. If you add features that do:
Cloudflare Pages → Project → Settings → Environment variables
Common future vars:
NEXT_PUBLIC_GA_ID— Google AnalyticsNEXT_PUBLIC_FORMSPREE_ID— Form handlerRESEND_API_KEY— Email sending (server-side)STRIPE_PRICE_ID_GROWTH— Subscription product ID
Variables prefixed with NEXT_PUBLIC_ are exposed to the browser. Anything sensitive must NOT have that prefix.
Local development
Run a single site
cd apps/mixed-greens
npm install --legacy-peer-deps # first time only
npm run dev
# Open http://localhost:3000
cd apps/agency
npm install --legacy-peer-deps # first time only
npm run dev
# Open http://localhost:3001 (different port, won't conflict)
Run both at the same time
Open two terminals, run npm run dev in each app folder. They run on 3000 and 3001 simultaneously.
Build & preview production
cd apps/mixed-greens
npm run build
# Inspect what's in: apps/mixed-greens/out
# To preview locally:
npx serve out -p 4000
# Open http://localhost:4000
Adding a new client site
The fast workflow once you have a few clients:
- Duplicate
apps/mixed-greens/→apps/<client-slug>/ - Update
package.jsonnamefield - Update
lib/restaurant.ts(or whatever data file applies) with their info - Replace placeholder images
- Update
app/layout.tsxmetadata - Add a new Cloudflare Pages project pointing to
apps/<client-slug> - Connect their custom domain
- Done — usually < 4 hours of work for a Starter, < 8 for Growth
Eventually, factor out the shared components into a local packages/ui and import them. Don't do this on day one — duplicating two or three times is faster than abstracting prematurely.
Forms (when you need them)
Both sites currently use mailto: for contact. This works but:
- Doesn't capture into a CRM
- Doesn't filter spam
- Loses people without a desktop email client
Upgrade options (in order of effort):
Easiest: Formspree / Web3Forms / Tally
- Sign up free
- They give you a form action URL like
https://formspree.io/f/abc123 - Replace your
<form>action with that URL - Submissions land in your inbox + their dashboard
- ~$10/mo paid tier removes branding
Middle: Resend + a Cloudflare Worker
- Resend gives you 3,000 free email sends/month
- Write a Cloudflare Worker that accepts POST and sends via Resend
- More control, no third-party form service
- Documented at: https://resend.com/docs
Heavy: Pages Functions / API routes (and lose static export)
- Switch to
output: 'standalone'in next.config - Add API routes
- Now you need a runtime, not just static
- Don't do this unless you have a real reason
Analytics (optional)
The free + privacy-respecting move:
- Plausible ($9/mo) or Fathom ($14/mo) — both load <1KB scripts, no cookie banners needed
- Add the snippet to
app/layout.tsx
If you want to be on the Google ecosystem:
- Google Analytics 4 + Google Tag Manager — free, but adds 50KB+ and requires a cookie banner in EU/CA jurisdictions
For client sites, GA4 is what most owners expect because it integrates with their Google Business Profile data. For your own agency site, use Plausible — looks more sophisticated.
Monitoring
Free options:
- UptimeRobot — free 50 monitors, 5-minute checks, email/SMS alerts. https://uptimerobot.com
- BetterStack (Better Uptime free tier) — pretty status pages too
- Cloudflare Analytics — built into Pages dashboards, gives you page views and core web vitals
Set up uptime monitors on every client domain. When something breaks at 9pm, you want to know before the client does. Customer trust is built in those moments.
Things that will go wrong (and how to fix)
"Build failed: image hostname not configured"
Add the hostname to next.config.ts:
images: {
remotePatterns: [
{ protocol: "https", hostname: "your-new-image-host.com" },
],
}
"Module not found" in deploy but works locally
Usually means a case mismatch (Mac is case-insensitive, Cloudflare's Linux build is case-sensitive). Check your import paths exactly match the file names.
Custom domain shows "Not Secure"
SSL provisioning takes a few minutes after adding the domain. If it's still failing after 30 minutes, check your DNS records — there's probably an existing A record fighting the CNAME.
Site loads but images don't show
You set output: "export" but forgot images: { unoptimized: true }. Add it.
npm install fails with peer dependency errors
React 19 + older Radix versions sometimes conflict. Use npm install --legacy-peer-deps. (Already in our build commands above.)
What "done" looks like
You're production-ready when:
- Both sites are pushed to GitHub
- Both Cloudflare Pages projects are deployed and green
- Both sites are reachable on a
.pages.devURL with HTTPS - At minimum the agency site is on a custom domain
- Uptime monitoring is on (UptimeRobot is free)
- You can do
git commit && git pushfrom your machine and see the change live in <5 minutes
Once that's true, you're ready to start dialing.