CASE · 062026 — ONGOINGSOLOSTATIC · pSEO · AD-REVENUE
◦ TOOLPRIME · toolprime.dev

73 thousand pages. One worker.

Suite of free dev/creator browser tools — static-first, pSEO-deep, ad-fundable. Astro 6 Islands, Cloudflare Workers + Assets, GSC-steered index. No backend, no tracking, one developer.

§ 01Problem · motivation

Why this exists.

The tool-portal market is functional but visually and editorially stuck in 2010. Slow, cluttered, cookie-banner hell.

Anyone looking to quickly generate a hash, crunch percentages, or compare time zones ends up today at Calculator.net, RapidTables, or UnitConverters. It works — but with ad banners that load before the tool, UI from another decade, and almost no editorial depth per calculator. The audience is end users who need the tool right now, don't want to install an app, don't want to sign up, and certainly don't want to click through a cookie manager.

ToolPrime is an attempt to fill that gap: modern, fast, programmatically deep — and kept viable as a one-person operation on ad revenue. No venture scale, no 40-person SEO agency behind it. One developer, one repo, and an infrastructure that handles the entire delivery on Cloudflare Workers Paid for $5/month, no matter how much traffic shows up.

§ 02Constraints · operating box

The box it had to fit in.

Every architectural decision had to pass these constraints.
C/01 · OPS
Solo dev. Code, SEO, deploy — all in one pair of hands. No team, no hand-off points.
C/02 · BUDGET
Infrastructure hard-capped < €10/month — realistically $5 CF Workers + domain.
C/03 · ZERO-ORIGIN
No DB, no backend server. Everything built statically or computed client-side.
C/04 · SCALE
~73,000 generated HTML pages — blows past the CF Pages 20K limit and GitHub Actions 504 timeouts.
C/05 · SEO = PRODUCT
Traffic has to come organically. Paid acquisition is uneconomical for ad revenue.
C/06 · PRIVACY
No tracking bloat, no third-party cookies — prerequisite for Mediavine/Raptive onboarding at 50K sessions/month.
C/07 · TRAFFIC
Spiky long-tail SERP, persistent baseline on the main tools. Extremely bursty, with no predictability.
C/08 · STATE
No sessions, no user accounts, no moderation. Every page load is stateless.
§ 03Architecture · pSEO feedback loop

How it runs.

Template × dataset → build farm → Wrangler → CF Workers edge. Then: GSC signal back into the analyzer, noindex filter into the template. The index breathes.
build.live·pages 73,359·indexable 20,812·noindex 52,547
deploy ·cost/mo $5
INPUT · 01
pSEO Template
/src/pages/[cat]/[a]-to-[b].astro
active13
CONTENT · 02
MDX collections
tool prose · per-category copy
formatfile · /never
DATASET · 03
Data matrix
n × n · currency · units
rows1,814
SITEMAP · 04
Crawl-budget config
noindex + sitemap-exclude
indexable~21k
BUILD · 05
Astro 6 · Islands
static-first · hydrate-on-need
THUMBS · 06
sharp · build-time
og · thumb · minify
assets73,473
SHIP · 07
wrangler 4 · ./deploy.sh
incremental manifest diff
last-deploy~30s
EDGE · 08
CF Workers + Assets
flat $5/mo · 330+ cities
cache-hit94%
OUTPUT · 09
Organic long-tail
privacy-first · mediavine-ready
sess/24h0
SIGNAL · 10
Search Console export
weekly ZIP · impressions/CTR
queries89,412
ANALYZE · 11
scripts/analyze-gsc.mjs
cannibalization detect
POSCTRCAT
FILTER · 12
Category kill-switch
noindex + sitemap-exclude
kicked52,547
EVENT LOG · ./deploy.sh · wrangler · analyze-gsc.mjs
04:18:22wranglerdeploy · 412 deltas · 28s · skipped 73,061 unchanged
04:18:14astrobuild · 73,359 routes · format:file · 187s
04:18:09analyzercannibal-detect · 18 cat · pos<5 · ctr<.5%
04:18:04filterkick · category:percent · 1,402 routes → noindex
04:17:58sitemaprebuilt · 20,812 indexable · 52,547 excluded
04:17:52sharpog · 73,473 thumbs · 1024×512 · cached
04:17:44gscexport · 7d · 89,412 queries · csv → /tmp
04:17:36tplcollected · 13 templates · n×n dataset · 1,814 rows
04:17:28cfworkers · req 12,408 · hit 94% · cost $0.00
04:18:22wranglerdeploy · 412 deltas · 28s · skipped 73,061 unchanged
04:18:14astrobuild · 73,359 routes · format:file · 187s
04:18:09analyzercannibal-detect · 18 cat · pos<5 · ctr<.5%
04:18:04filterkick · category:percent · 1,402 routes → noindex
04:17:58sitemaprebuilt · 20,812 indexable · 52,547 excluded
04:17:52sharpog · 73,473 thumbs · 1024×512 · cached
04:17:44gscexport · 7d · 89,412 queries · csv → /tmp
04:17:36tplcollected · 13 templates · n×n dataset · 1,814 rows
04:17:28cfworkers · req 12,408 · hit 94% · cost $0.00
§ 04Decisions · trade-offs

Four deliberate choices.

Per decision: what was chosen, instead of what, why.
D/01

Astro 6 static-first + Islands instead of Next.js SSR.

chosen
All tool pages pre-rendered, interaction only in React islands — no server runtime in the request path
instead of
Next.js SSR/ISR with Node runtime on every request
reason
With ad revenue, every cent of compute counts. SSR would have cost compute-minutes per tool interaction and undermined CDN caching. Static + Islands gives Lighthouse-100 out of the box, the CDN caches everything, and the React islands hydrate only where real interaction is needed — not in the tool description and not in the footer.
D/02

Cloudflare Workers + Assets instead of Pages or Vercel.

chosen
CF Workers Paid $5/mo · Wrangler 4 · up to 100K files
instead of
CF Pages (20K limit) · Vercel (traffic-scaled pricing)
reason
Pages fails hard at the 20K file limit — and we're producing 73K pages. Vercel scales pricing with traffic, which becomes unpredictable with spiky long-tail SEO. Workers Paid is flat: $5/month, whether it's 10K or 10M requests. For an ad-revenue model that's existential — fixed cost under variable income.
D/03

Local deploy via ./deploy.sh instead of GitHub Actions.

chosen
Wrangler locally, incremental upload — CI only as a workflow_dispatch backup
instead of
GitHub Actions push-on-main with full deploy
reason
Actions reliably timed out with 504 Gateway Timeout while uploading 146K build assets to Cloudflare. Not theoretically — reproducible fail. Wrangler locally pushes incrementally, sees what's changed, and uploads only deltas. CI is now an emergency backup, not the hot path.
D/04

Programmatic SEO as template × dataset.

chosen
One .astro template per category, n × n combinations from data → thousands of long-tail pages
instead of
Hand-written pages per tool combination
reason
You don't serve the long-tail SERP with 40 hand-written pages. The template approach serves real search intent (e.g. USD to EUR, EUR to JPY …) in one commit. And — crucially — it allows evidence-based steering: if a category doesn't rank, it gets completely switched off via noindex + sitemap filter, without deleting any code. Category on, category off — one config flag.
§ 05Highlights · interesting bits

Things that were not obvious.

Edge cases and details that only became clear while building.

GSC analyzer against widget cannibalization

H/01
Google ranks pSEO pages like "5% of 200" at position 8–10 — with 0% CTR, because Google's own calculator widget answers directly in the SERP. Traffic illusion on position, zero visitors.

scripts/analyze-gsc.mjs parses GSC export ZIPs, automatically finds categories where position is good but CTR is null — and flags them for noindex. Result: ~52,000 pages dropped from sitemap + noindex'd, crawl budget redirected to the ~21,000 pages that aren't cannibalized by Google's own widgets. Recovery window per GSC trend: 4–8 weeks — not a quick win, just hygiene.

Canonical duplicates via build.format: 'file'

H/02
By default Astro emits /tool/index.html with trailing-slash redirects. At 73K pages that's 343 canonical duplicates — same URL, different normalization, visible as coverage errors in GSC.

Switching to build.format: 'file' + trailingSlash: 'never' produced flat .html outputs without an intermediate redirect. One commit, the problem was gone. Sometimes the fix is a config flag you should have set four months ago.

Zero-origin despite file processing

H/03
Image compressor, PDF tools, ZIP extractor — everything that normally needs an upload runs entirely in the browser. browser-image-compression, jszip, html2pdf.js. sharp only at build time for thumbnails.

No upload, no backend, no GDPR headache — user data never leaves the browser. Requires clean web-worker handling for large files so the UI doesn't freeze. But it saves every thought about storage, moderation, abuse, GDPR DPAs, and processor agreements. The best server is no server.

Wrangler push as the deploy hot path

H/04
At ~73K build assets, every upload mechanism becomes a question of quality of life. Wrangler 4 finally has incremental manifest diffs — it only uploads files that have changed against the CF-side state.

That completely changes the deploy loop: a template fix in one tool deploys in ~30 seconds instead of 8 minutes. That allows iterative fixing directly against production — which, in ad-revenue terms, also means repairing broken revenue pages in 30 seconds instead of waiting out a CI run.
§ 06Stack · in production

What's running.

Working toolchain in production — nothing theoretical.
Astro 6React IslandsTypeScriptMDX · Content CollectionsCloudflare Workers + AssetsWrangler 4pSEO Templatessharp (build-time)browser-image-compressionjszip · html2pdf.jsGSC AnalyzerSitemap filterPrivacy-first · no tracking
§ 07Reflection · takeaways

What I learned.

Project is running. These are the things I'm taking into the next ones.

SEO is a feedback system, not an upload.

I started with the illusion that you "build and deploy" pSEO. Reality: you build, deploy, measure, kick half of it back out, deploy again. The GSC analyzer was the difference between a tool page and a tool index. Without the feedback loop I would have kept 73K pages alive that together bring in less traffic than 21K focused ones. Every pSEO project needs that back channel from day 1 — not only once the index is too big for the crawl budget.

Flat pricing is a feature.

Workers Paid at $5 flat cleared my head. No revenue forecasts anchored to infra anxiety, no working out worst-case DDoS bills. The traffic is allowed to explode, the cost model stays the same. That's gotten rare — and for a solo developer still running SEO analyses at night, worth more than any premium-feature blog post.

◦ NEXT CASE · 07 / 11
Ludotek
← all projects