Skip to content
netweft

How netweft is built, and a prompt to build your own

Engineering 5 min read

netweft is four Cloudflare Workers over one D1 database: static marketing and dashboard sites, a Hono API with Workflows and a cron, and an analytics relay. Six decisions shaped it: a provider port, reserve-then-settle credits, stored snapshots, one-statement D1 guards, a read-only MCP surface and no free tier.

netweft is an SEO research and rank-tracking product that one person can run. This guide lays out how it is built, explains the six decisions that shaped it, and ends with a prompt you can hand to an AI coding agent to build a product of the same shape.

The pieces

Everything runs on Cloudflare, as four Workers in one Bun and Turborepo workspace:

Worker What it is
Marketing an Astro site rendered to static files at build time
Dashboard a TanStack Start single-page app, served as static assets
API Hono with an OpenAPI 3.1 document, two Cloudflare Workflows and a five-minute cron
Relay a small Worker that forwards consented analytics

The API owns all state: a D1 database through Drizzle ORM, a KV namespace for caching vendor answers, and Workers Rate Limiting for request limits. Sign-in is better-auth with Google, GitHub and email codes, payments are Polar, SEO data comes from DataForSEO, AI calls go through OpenRouter and email through Resend.

The dashboard and the API are separate origins on the same site, so a SameSite=Lax session cookie works across them without a token in the browser.

Decision 1: the data vendor sits behind a port

One module knows DataForSEO exists. Everything else calls an interface with two implementations: the real adapter, and a mock that serves fixtures and needs no account. The whole product runs locally and in tests with no vendor key, and a vendor change touches one folder. See the provider sits behind a port.

Decision 2: credits are reserved, then settled

Every paid call holds its price before it runs and settles at what the vendor actually billed, never more than the hold. A workspace that cannot cover the hold is refused before money is spent. Lists are held at their 200-row ceiling; rank checks are priced per page of ten results, because that is how the vendor bills them. See reserve, then settle and the rate card.

Decision 3: rankings are stored observations

A rank check writes a snapshot per keyword and device: position, URL and the time it was seen. History is the list of snapshots, never a recomputation, so last month’s chart cannot change. See snapshots, not recomputation.

Decision 4: D1 guards are single statements

D1 has no interactive transactions, so every check-then-write is one conditional statement (INSERT … SELECT … WHERE the balance still covers it), one atomic batch, or a short leased lock row. Long work runs in Workflows, which retry steps and survive a Worker being recycled, and the cron starts scheduled checks and ends runs whose Workflow is gone.

Decision 5: agents read, people spend

An MCP server lets an agent read a workspace with a scoped API key. All 25 tools are read-only over stored rows, so a leaked key can read but never write or spend. The in-app assistant uses the same tools and must quote only figures a tool returned.

Decision 6: no free tier, a trial and a demo instead

Every lookup costs real money, so a free tier pays for accounts that never convert. netweft offers a short trial with a tighter rate limit and a read-only demo that answers from sample data. See why netweft has no free tier.

A prompt to build your own

Paste this into an AI coding agent such as Claude Code in an empty repository. Replace the bracketed parts. It builds in phases, and each phase ends with tests passing before the next starts.

Build a metered SEO SaaS for [your niche] as a Bun + Turborepo monorepo on
Cloudflare Workers. Work in phases; finish each with passing tests and a
commit before starting the next. Ask me before choosing anything not listed.

Stack:
- packages/api: Hono with @hono/zod-openapi on a Worker, D1 via Drizzle ORM,
  KV for caching, Workers Rate Limiting, Cloudflare Workflows for long jobs,
  a cron trigger every 5 minutes. Vitest with @cloudflare/vitest-plugin
  against a real local D1.
- packages/web: TanStack Start as a static SPA, TanStack Query, Tailwind,
  shadcn/ui, a typed client generated from the API's route types.
- packages/db: one Drizzle schema and its migrations, shared by api and web
  test fixtures.
- Auth: better-auth with organizations (workspaces), Google sign-in and
  email + password confirmed by a 6-digit code.
- Payments: Polar subscriptions and one-off top-ups, mirrored by webhook.

Rules that must hold everywhere:
1. The SEO data vendor ([DataForSEO or another]) sits behind a provider
   interface with a deterministic mock; nothing outside the adapter imports
   the vendor client. The app must run end to end with the mock.
2. Credits: 1 credit = a fixed fraction of a dollar of vendor cost. Before any
   paid call, insert a hold with ONE conditional SQL statement that succeeds
   only if balance minus open holds covers it; refuse with 402 otherwise.
   After the call, settle at the vendor's reported cost, capped at the hold.
   Price every operation per the vendor's real billing unit, and hold list
   lookups at their maximum page size.
3. D1 has no interactive transactions: use single conditional statements,
   db.batch, or a leased lock row for every check-then-write.
4. Rank checks store immutable snapshots (keyword, device, position or null,
   URL, timestamp). Never recompute history.
5. Expose a read-only MCP server at POST /mcp authenticated by hashed,
   scoped API keys. No tool writes or spends credits.
6. Every outbound fetch of a user-supplied URL passes an SSRF guard that
   resolves DNS and refuses private addresses, follows redirects manually
   and caps the body size.
7. No free tier: plans with a trial and trial credits, plus a read-only demo
   workspace that answers paid lookups from sample data.

Phases:
1. Monorepo, D1 schema, auth, workspaces and projects (domain + market).
2. Credit ledger, holds, settlement, Polar plans, top-ups and webhook.
3. Keyword research and saved keywords through the provider port.
4. Rank tracking: trackers, Workflow-run checks, snapshots, cron schedule.
5. Site audit: own crawler in a Workflow, robots.txt, issues by severity.
6. Dashboard screens for each of the above, then the MCP server.
7. Marketing site, docs and the read-only demo.

The prompt encodes the decisions above as rules rather than suggestions, because an agent follows a numbered rule more reliably than a paragraph of reasoning. Expect to review every phase: the rules prevent the expensive mistakes, and the review catches the rest.

Every claim in these guides is something the product does.

Start a trial and check the ones that matter against a domain you already know.

There is no free tier. Three plans from $50 a month, each opening with a free trial.