Ecommerce Site Cost: Four Decisions, Not a Quote
Agency guides price the build. The recurring bill is set by four architectural choices — and here they are itemised from a live storefront: 111 products, 288 static pages, 13 dependencies.
Agency pricing guides answer "how much does an ecommerce site cost" with a build number — $10,000 to $250,000, plus $3,600 to $50,000 a year to maintain. Those ranges are real, and they are also the wrong unit. A store's cost is not a quote; it is four architectural decisions that each set a recurring bill. This is the bill for a live storefront with 111 products, 296 prerendered pages and 13 runtime dependencies, itemised from its own repository.
A note on what follows: every number about this site is measured from this checkout. We are deliberately not quoting current plan prices for Vercel, Supabase or Resend — those change, and we would be reciting rather than measuring. What we can show is the thing that actually determines your bill, which is which meter each decision puts you on.
The four decisions that set the bill
| Decision | Cheap answer | Expensive answer | What it actually meters |
|---|---|---|---|
| Where product data lives | A file compiled into the build | A database queried per request | Query volume, connection count, and whether traffic can hurt you |
| How pages render | Prerendered to a CDN | Server-rendered per visit | Compute invocations |
| How images are served | Derivatives built once at deploy | An optimiser resized per request | Per-image transformations, the classic surprise line |
| Who is the seller of record | A merchant-of-record processor | You, plus a tax stack | Percentage fees vs. registrations and filings |
Nothing on that list is about how much design you buy. They are the lines that keep charging after launch, and the difference between the ends of each row is one or two orders of magnitude — which is why "what does an ecommerce site cost" has a four-figure answer and a six-figure answer for the same store.
1. The catalog is a file, not a database
The single most expensive default in ecommerce is that a product catalog is assumed to need a database. Ours does not:
$ wc -l src/data/catalog.ts
8114 src/data/catalog.ts
$ grep -c 'slug: "' src/data/catalog.ts
111
111 products in one typed TypeScript array. Not a row in Postgres, not a document in Mongo, not an entry in a headless CMS. It compiles into the build. The product pages that come out of it are static HTML files.
What that removes from the recurring bill: every catalog read. A product page being crawled, shared, or hit by a traffic spike costs a CDN response, not a query. There is no connection pool to size, no read replica, no cache layer to operate, and no scenario where a good day on Hacker News turns into a database bill.
What it costs instead is that a catalog change requires a deploy, and that content editors need a pull request. That is a real trade — we argue it properly in MDX vs a headless CMS, and the shape of the data is the deciding factor rather than the app, which is the case we make in Supabase vs MongoDB. A file is right when a human decides the catalog changes; a database is right when the system does — inventory that decrements, prices that move hourly, stock that must be authoritative.
The dividing line is sharper than most guides admit, and it has a name: stock. A static catalog cannot tell you truthfully whether the last one is sold. We wrote up exactly where that breaks in product variants without a commerce backend. If overselling would cost you a customer, this row of the table is not available to you and you should price the database in.
2. Almost nothing runs on a server
From a production build of this repo:
| Rendering mode | Routes | What it costs per visit |
|---|---|---|
Prerendered static (○ and ●) | 296 pages | A CDN response |
Server-rendered on demand (ƒ) | 8 routes | An invocation |
The eight are the ones that must be: /dashboard, /dashboard/settings, /login, /signup, /reset-password, /auth/callback, /api/download and the LemonSqueezy webhook. Every one reads a session, a secret, or a request body. Everything a shopper browses before they buy — 111 product pages, seven category hubs, the catalog grid, pricing, the blog — is a file.
This is where the maintenance-cost ranges in the agency guides come from. A store whose product pages are server-rendered pays compute proportional to traffic including crawler traffic, and a 111-product catalog attracts a lot of crawler traffic. The same store prerendered pays for 296 files once per deploy.
The trap is that the choice is easy to lose by accident. Reading cookies, headers or searchParams opts a route out of static rendering silently — no error, no warning, one character different in the build output. That is the whole subject of what silently turns a page dynamic, and on a storefront it is the difference between a flat hosting bill and one that tracks your traffic.
3. The image line item, which is where stores get surprised
Product photography is the biggest asset class in any store and the easiest place to sign up for a per-request meter without noticing. Measured across public/ in this repo:
| Asset class | Files | Bytes |
|---|---|---|
Cover JPEGs (SEO surface: og:image, Product schema) | 446 | 61.5 MB |
| Pre-built WebP derivatives (what browsers actually load) | 684 | 30.6 MB |
Total in public/ | — | 95 MB |
The important part is not the totals, it is the split. No component in this codebase renders a cover JPEG. The full-size JPEG exists because og:image and the Product structured data need a real, full-resolution URL. Every <img> on every page points at a derivative instead — a 1060-wide card variant or a 1600-wide detail variant — resolved by cardImage() and viewImage() in src/lib/imageVariants.ts and generated ahead of time by scripts/images/build-variants.ts.
Because those derivatives are built at deploy, there is no request-time image optimiser in the request path and therefore no per-transformation image bill at all. A closed image set — a catalog you control, not user uploads — does not need an optimiser; it needs a build step. The mechanics and the before/after numbers are in image optimization without next/image.
Two structural points for anyone pricing a store:
- Per-image-transformation pricing scales with your catalog times your breakpoints times your crawlers, not with your sales. It is the line item most likely to be larger than your hosting.
- Storage is nearly free and bandwidth is cheap at the CDN. 95 MB of assets is not a cost problem. Resizing them on demand, repeatedly, is.
4. Merchant of record vs. doing your own tax
The last decision is the one with no engineering component and the largest range.
This store sells through LemonSqueezy, which is a merchant of record: it is legally the seller, so it registers, collects and remits sales tax and VAT in the jurisdictions your buyers live in. The price of that is a percentage of each order rather than a fixed monthly fee — a cost that is zero until you have revenue and rises with it.
The alternative is a payment processor where you are the seller. The processing fee is lower. What you have taken on is the tax obligation, which is not a fee, it is a function: registrations, thresholds, filings, and a tax-calculation service. We did the arithmetic on a real order in LemonSqueezy vs Stripe for digital products — on a $39 order the fee gap is $0.82, and what that buys is a registration you no longer hold.
For a store doing a handful of orders a month, the percentage difference is noise and the compliance difference is everything. For a store at real volume the arithmetic inverts. This is a revenue-dependent decision, and it is the only one on this page that is.
What the commerce layer actually amounts to
There is a persistent belief that the checkout, accounts and download-gating half of a store is where the money goes. In this repo the entire commerce backend is:
$ ls supabase/migrations/
0001_commerce_init.sql 0005_retain_anonymized_download_audit.sql
0002_display_name_allowlist.sql 0006_pricing_v2_slot_kinds.sql
0003_commerce_rpcs.sql 0007_atomic_download_rate_limit.sql
0004_lock_down_rate_limit_fn.sql 0008_refund_requests.sql
Eight migrations: schema, row-level security, atomic RPCs, a rate limiter, an audit trail and refund requests. Plus a signed webhook route and a 75-line entitlement engine. The whole runtime dependency list for the storefront and the commerce layer is thirteen packages:
"@mdx-js/loader", "@mdx-js/react", "@next/mdx",
"@supabase/ssr", "@supabase/supabase-js",
"@vercel/analytics", "lucide-react",
"next", "react", "react-dom",
"rehype-slug", "remark-gfm", "server-only"
Three of those are MDX for the blog. Two are Supabase. One is an icon set. There is no UI library, no form library, no state manager, no carousel, no modal library, no search service — a census we ran properly in when hand-rolling 91 components actually wins and again in storefront search without a search service.
Dependencies are not a direct cost. They are a proxy for two real ones: the surface you must keep patched, and the paid services that tend to arrive attached to them.
So what does it cost?
Honestly: the recurring infrastructure cost of a store built this way is a small number of platform subscriptions, several of which have usable free tiers at low volume, plus a percentage of revenue to the payment provider. That is the whole ongoing bill, and it does not move much with traffic because traffic hits a CDN.
The costs that are actually large are the ones the agency ranges are measuring, and they are worth naming plainly:
| Cost | Scale | What moves it |
|---|---|---|
| Design + build | $1k → $250k+ | Whether you start from a template or a blank page |
| Ongoing engineering | The bulk of "maintenance" | How much bespoke code you own |
| Payment processing | A percentage of every order | Merchant-of-record or not — see the fee arithmetic linked above |
| Infrastructure | Usually the smallest line | The four decisions above |
The first row is where a template earns its money and it is the one we are obviously interested in, so read the bias as declared: we sell storefront templates. The honest version of that argument, including the four-to-nine weeks of UI work a template does and does not save, is template vs building from scratch. And if what you want is a hosted platform that does the whole job for a monthly fee, Shopify vs a Next.js storefront is the comparison to read before any of this matters — for most people opening a store this year, that post says Shopify, and means it.
For reference, our own prices are on the page: $39 for one premium template, $129 for all of them, $249 for everything plus the backend starter — one-time, from src/data/pricingTiers.tsx. We publish them for the same reason we published the byte counts above: a cost guide that will not state its own numbers is a brochure.
Common mistakes
| Mistake | Symptom | Fix |
|---|---|---|
| Pricing the build, not the meters | Launch on budget, bill grows with traffic | Price the four decisions above |
| A database for a catalog a human edits | Query cost and ops for data that changes weekly | A typed file, deployed |
| A static catalog for authoritative stock | Overselling | A backend — the one place to spend |
| Request-time image optimisation on a closed image set | The largest line on the invoice | Build derivatives at deploy |
| Reading cookies on a product page | Silently server-rendered, compute per crawl | Keep shopper-facing routes static |
| Choosing a processor on fee alone | A tax obligation you did not price | Merchant of record until volume inverts it |
| Counting storage as a cost | Optimising 95 MB that costs nothing | Storage is cheap; per-request work is not |
| No analytics on the funnel | Cannot tell which cost is buying anything | Measure conversion before optimising bill |
Frequently asked questions
Is a template-based store actually cheaper, or just cheaper up front? Cheaper up front, and neutral afterwards — with one condition. A template's savings survive only if you can still take its updates, which depends on how you customise it. The edit order that keeps both possible is in how to customize a website template without blocking its updates.
At what point do I need a real backend? When something must be authoritative in a way a file cannot be: stock levels, per-customer pricing, order state. Accounts and downloads do not force it as early as people expect — ours is eight SQL migrations. Overselling is what forces it.
What is the most commonly underestimated recurring cost? Image transformations, by a distance. Catalog size times breakpoints times crawler traffic is a much larger number than sales volume, and it is metered per request unless you design it out.
Does static rendering limit what a store can do? Not for the shopping half. Every page a visitor sees before checkout can be static; the cart, the session and the checkout are the dynamic parts. Where cart state should live — cookie, database row, or context — is its own decision, covered in where cart state should live.
Storefront templates built this way
ASoc Bloom is a wellness and supplements store across six categories with quick-view, cart, wishlist, checkout and account pages. ASoc Bumble is an organic baby-clothes storefront with bundle sets, sale and outlet sections and a working cart. ASoc Circuit is the large one — a multi-department electronics marketplace with a full mega-menu, rotating deals and category tiles, which is the stress test for whether a static catalog architecture holds up at breadth.
Browse the full sets of Next.js shop templates and Tailwind shop templates, or read how to evaluate a website template before you buy for the ten checks worth running on any of them — ours included. For the same cost model narrowed to one vertical — delivery slots, substitutions, live stock — see what a grocery website costs.
