DEV Community

Cover image for How do you sell a feature on a site with no backend?
Shun Kimura
Shun Kimura

Posted on

How do you sell a feature on a site with no backend?

A few weeks ago I shipped Parquet Diff — a tool that joins two Parquet files on a key column and shows you exactly what changed: added rows, removed rows, changed cells, and schema drift (type changes, added/removed columns). It's part of ParquetKit, a Parquet viewer/SQL workbench/converter that runs 100% client-side — no upload, no backend, files never leave the browser.

This week I added a paid tier to it: the free version shows you a summary and the schema diff, a $15 one-time unlock exports the full row-level report (every added/removed/changed row) as HTML or Markdown. The interesting part wasn't the feature — it was that the site has no server, and I didn't want to add one just to sell something.

The constraint

next.config.ts has output: "export". Static files, no API routes, no database. That's not incidental — it's the actual privacy guarantee. The pitch to users is "open DevTools, watch the network tab, your file never leaves your machine." Adding a backend to handle payments would mean either breaking that promise for the paid tier, or running two architectures side by side. Neither felt right.

What actually worked

Polar.sh turned out to have exactly the right shape for this. It's Merchant of Record (handles VAT/sales tax so I don't have to), and — the part that made this possible — its license key validation endpoint is designed to be called from an untrusted client:

POST https://api.polar.sh/v1/customer-portal/license-keys/validate
{ "key": "...", "organization_id": "..." }
Enter fullscreen mode Exit fullscreen mode

No auth token required. It's meant for exactly this: desktop apps, browser extensions, static sites — anything that can't hold a secret. You send the key string, you get back granted / revoked / disabled.

So the flow is: Polar-hosted checkout → buyer gets a license key by email → pastes it into the tool → the browser calls that endpoint directly → localStorage remembers the result. No server on my side ever sees the request. The only thing that crosses the network for the paid tier is the license key string — never the file, never its contents. The privacy story holds for the thing you're actually paying to unlock.

The free tier isn't a teaser stub

The free report (summary counts + schema diff) is a real, complete Markdown export — I didn't want "free" to mean "crippled." If the row export never sells a single unlock, the free diff tool and free report are still useful on their own, which felt like the honest way to bet on this instead of gating everything behind a wall from day one.

Try it

parquetkit.com/diff — there's a one-click sample pair if you don't have two Parquet files handy to compare.

MIT licensed: https://github.com/XxxKMSxxX/parquetkit

Curious what other people building small paid features on static/serverless sites have landed on — this is the first time I've shipped a paywall without writing a single line of server code, and I'm not sure it's the "right" way, just the way that kept the architecture honest.

Top comments (0)