Full stack development

How We Built the Jev Website Scanner, a Paid Web App

We built the Jev Website Scanner as our own product: a web app that reads a whole website, saves its progress in a Cloudflare D1 database, sells a $99 audit through Stripe Checkout and emails the report. This build log covers the decisions and the numbers, and a free scan lets you check them.

Discuss your project

Where does one scan go, step by step?

Pick a step to light the path up to it. Each step is a real part of the scanner’s code.

The scanner’s four layers as lanes, and the path one scan takes through them.
  1. DiscoveryBrowser · API

    Reads robots.txt and the home page, then up to 30 sitemaps, pages that sell first.

  2. Saved progressDatabase

    The page list goes into D1, so a closed tab resumes from the report link.

  3. One free scanDatabase

    A unique index gives each domain one free scan, even with two tabs open.

  4. PaymentIntegrations

    Stripe Checkout sells the $99 audit and $19 rescans, and a paid scan starts once Stripe confirms payment.

  5. Reading pagesIntegrations

    5 pages a step, at most 4 Jev calls in flight, 2 lanes at once.

  6. Speed and emailIntegrations

    Google PageSpeed runs the audit's speed test, and Resend emails the report link.

  7. LimitsAPI

    Turnstile, plus daily limits per connection and per email address.

  8. CleanupDatabase

    Unstarted drafts go after 7 days, started scans after 12 months.

  9. ReportBrowser

    A PDF built in the browser with pdf-lib, plus CSV exports.

What does each plan read?

Jev Website Scanner plans, from the scanner’s own price file.
PlanPages it readsPrice
Free scan100Free
Full auditUp to 3,000$99 one time
Competitors in the audit3 × 25 = 75Part of the $99 audit
Rescan after fixesUp to 3,000$19 per rescan

70 checks in all, and each page gets the ones that fit its type

44 questions Jev answers26 facts code counts

Built and run by Trojan. Jev is TypeSafe’s AI model, and TypeSafe doesn’t endorse this app.

How We Built the Jev Website Scanner, a Paid Web App
The Jev Website Scanner start screen, with a box for a website address and a Scan my website button

Explore the composition, then select a detail to look closer.

We built the Jev Website Scanner as our own product. It's a web app that reads a whole website, saves its progress in a Cloudflare D1 database, sells a $99 audit through Stripe Checkout and emails the report link through Resend. This is the build log: how one scan moves through the app, the decision behind each step and the numbers in the code. You can check any of it by running a free scan at trojandigitalmarketing.com/website-scan.

The scanner is built and run by Trojan Digital Marketing. It isn't affiliated with or endorsed by TypeSafe, which makes Jev, the AI model the scanner asks about each page. No client paid for it, and it's the app we point to when someone asks what we can build.

What did the scanner have to do?

An owner types in a website and gets back a page by page answer to why Google and AI tools do or don't pick those pages, with a fix list in plain language. That sounds like one request, but a real site can have thousands of pages, and reading them takes far longer than a single web request is allowed to run. So the requirements came out like this:

  • Read up to 3,000 pages of someone else's site politely, following its robots.txt.
  • Survive a closed tab or a phone going to sleep halfway through.
  • Sell a paid audit to someone with no account and no password.
  • Keep spending on outside services capped, since every page costs a call to Jev.
  • Hand over a report an owner can act on and a spreadsheet a developer can sort.

How does one scan move through the app?

The drawing at the top of this page follows these nine steps, in the order the code runs them.

  1. Discovery. The scanner checks robots.txt, reads the home page, then works through up to 30 sitemaps to find the pages that sell, like services, towns, about and contact, ahead of articles. If it meets a bot check, it stops.
  2. Saved progress. The page list goes into Cloudflare D1 as soon as it's found, and every page read is saved as it finishes. A closed tab picks up where it stopped when the report link is opened again.
  3. One free scan per domain. A unique database index enforces it, so two tabs racing each other can't both claim it.
  4. Payment. Stripe Checkout sells the $99 audit, which covers up to 3,000 pages, 3 competitors you name, a speed test and one rescan, plus $19 rescans after that.
  5. Reading pages. Each step reads 5 pages with at most 4 calls to TypeSafe's Jev API in flight, which keeps a scan under Jev's per-minute limit.
  6. Speed test and email. Google PageSpeed Insights runs the speed test, and Resend sends the report link.
  7. Limits. Daily limits are counted per connection and per email address, and Cloudflare Turnstile screens out bots before a scan starts.
  8. Cleanup. Scans nobody started are deleted after 7 days, and started scans after 12 months.
  9. The report. Results export to CSV files and to a PDF that's built in the visitor's browser.

Why does the visitor's browser drive the scan?

Cloudflare Workers are built for short requests, and a 3,000 page scan is a long job. We could have added a queue and a background worker. We chose to let the open scan page ask for the next batch again and again, and to keep every bit of progress in D1 rows that a request claims before it works on them.

Two lanes run at once, and each asks for 5 pages at a time. If a request dies halfway, the pages it claimed count as stale after 6 minutes and the other lane takes them. The trade-off is that a scan only reads pages while its tab is open. The report link is what makes that acceptable: open it again on any device and the scan resumes from the last saved page.

The same idea fits any long job in a small business app, like importing a big spreadsheet or sending a month of invoices. Save the progress where the next request can find it, and a lost connection only costs one retry.

How does the app stop two tabs from claiming one free scan?

Connect an answer to its evidenceUsefulQuestionAnswerEvidence
Question
Identify what the customer needs to know.
Answer
Explain it clearly.
Evidence
Support the answer with a source.
Connect an answer to its evidenceA useful answer explains the question, supports the claim and identifies the source.

With the database. The obvious way to write that rule is a check in code: look for an earlier free scan, and start a new one if there isn't one. Two requests arriving together would both pass that check. A unique index on the domain, limited to free scans, makes the second insert fail however the requests are timed:

CREATE UNIQUE INDEX jev_scans_free_domain
  ON jev_scans (domain) WHERE kind = 'free';

When a free scan reaches 12 months and is deleted, its domain is copied into a small table of its own first, so that domain's one free scan stays used. A second unique index, on the Stripe checkout session, does the same job for payments: one payment pays for one scan.

We lean on rules like these whenever two tabs or two retries could collide. A check in code is easy to get wrong under load, and a constraint in the database can't be skipped.

How does the $99 audit get paid for?

Through Stripe Checkout, so card details never touch our server. The scanner talks to Stripe's API with plain requests and no SDK. It sends an idempotency key with each checkout, so a double click can't create two sessions, and it finds the price in our Stripe catalog by its lookup key, so each sale shows under the right product.

The part that matters most is the webhook. When a payment finishes, Stripe calls our endpoint, and that call counts only if it proves it came from Stripe. Our code computes an HMAC-SHA256 signature of the timestamp and the raw body with the webhook secret, compares it with the Stripe-Signature header in constant time, and rejects anything more than five minutes old. That's the check Stripe's webhook documentation describes. The return page doesn't take the browser's word for a payment either. It asks Stripe's API for the checkout session before the paid scan starts.

How does it stay under Jev's rate limit?

Every page costs a call to TypeSafe's Jev API, which answers the questions the scanner asks about that page. Jev has a per-minute limit, and a whole site can mean thousands of calls. So a step reads 5 pages with no more than 4 Jev calls in flight, and with 2 lanes running, a scan stays near the limit without crossing it.

When a call fails, the client tries again, up to 5 attempts in all. It waits a little longer each time, with some randomness so retries don't bunch up, and it honors the wait Jev asks for when Jev sends one. A public scan also gets 3 retries of its own before it pauses and tells the visitor what happened.

What keeps the free scan from being abused?

A free tool that calls a paid API runs up a bill with every scan, so we designed the limits along with the first screen.

  • Cloudflare Turnstile checks that a person is starting the scan. Most visitors never see it.
  • Each connection gets 20 site lookups, 3 free scans and 20 checkouts a day. Connections are stored as a hash, and IPv6 addresses count by their /64 block, so rotating through addresses on one network doesn't reset the count.
  • Each email address gets 3 free scans a day and 10 a month. Addresses count as one inbox, so a +tag or extra dots in a Gmail address don't make a new one.
  • A DNS lookup checks that the email's domain can receive mail, which catches typos before a report goes nowhere.
  • The whole site allows 300 free scans and 2,000 lookups a day, which caps the worst day's spend.

Public scans also respect the sites they read. They follow robots.txt, stay on the scanned site through redirects and never use a headless browser, so another site's bot check is left alone.

What happens to the data after a scan?

We wrote the retention rules before launch, and a scheduled job runs them every 15 minutes. Started scans are kept 12 months so an owner can come back and compare a rescan, then deleted along with their pages, links and email. Drafts nobody started go after 7 days, or 30 days when they went to checkout, since a bank payment can take days to settle. Daily limit counters are deleted the next day.

Page text goes to TypeSafe to be scored, the page address goes to Google PageSpeed Insights, payments go through Stripe, and for a paid audit the weakest page's text goes to an outside AI writing service for the before and after rewrite. Our privacy policy names each one.

What does the report look like?

Every page gets a score in seven areas: Sounds like you, Hyper local, Proof and trust, Answers and AI, Turns visits into calls, Search fit, and Technical basics. Jev answers 44 set questions about each page, and code counts 26 technical facts exactly, like schema, tap-to-call links and title length. A finding needs an 80% yes from Jev to make the findings list, and weaker ones go to a separate "Worth a look" list.

The PDF opens with a two-page owner summary and puts the detail in an appendix. It's built in the visitor's browser with pdf-lib, so our server never renders it. The CSV has a row for every page, which a developer can sort by whatever they're fixing first.

What did it find when we scanned our own site?

Make account access explicitYourbusinessDomainWebsiteAnalyticsRecords
Domain and website
Document ownership and access.
Analytics and records
Define access and the handover.
Make account access explicitDomain, website, analytics and customer records need clear ownership and a workable handover.

On September 25, 2026, we ran it on trojandigitalmarketing.com. It read 854 pages and scored 842 of them. These are the category averages:

CategoryAverage scorePages scored
Technical basics, counted by code
99.5
842
Proof and trust
70.2
842
Search fit
66.5
842
Answers and AI
59.7
842
Turns visits into calls
56.2
842
Sounds like you
39.8
842
Hyper local
30.8
366

The technical average has a median of 100, and it's the part code controls. The overall health average was 63.1, with 115 pages rated Good and 727 rated Improve. Our low scores are about how the pages are written, especially how local and personal they sound, and the scan's fix list is the plan we're working through now. Hyper local only applies to home, service, town and contact pages, so articles and case studies aren't scored on it, which is why it covers 366 pages.

What would we carry over to your app?

Most of these decisions have nothing to do with scanning websites. They're habits that fit any app a small business depends on:

  • Put the rules that must never break into the database, like one free scan per domain or one scan per payment.
  • Check the signature on every webhook before trusting it, whether it comes from Stripe, Resend or anyone else.
  • Cap every paid outside service in code, per visitor and per day, before launch.
  • Save progress on long jobs where the next request can find it.
  • Write down how long you keep each kind of data, and let a scheduled job do the deleting.

If one of those sounds like the problem in your office, our full stack web app page shows what we'd build first.

What isn't in this build?

  • There's no background queue. A scan reads pages only while its tab is open, and the report link resumes it.
  • It reads the text in a page's code. It doesn't look at photos or run the site's JavaScript.
  • Public scans can't read sites that serve pages in a character set other than UTF-8.
  • Subdomains count as separate domains for the free scan.
  • It covers the website only, so a Google Business Profile and reviews are outside it.

The scanner is about 4,600 lines of TypeScript across its files, with 93 test cases, 43 of them against a stand-in for the D1 database.

Questions, answered.

Did a client pay for the Jev Website Scanner?

No. It's Trojan's own product, built and run by us. It isn't affiliated with or endorsed by TypeSafe, which makes Jev.

What is the Jev Website Scanner built with?

TypeScript and React 19 on Cloudflare Workers, with Cloudflare D1 holding scans, pages, links and limits. It uses Stripe Checkout for payments, TypeSafe's Jev API for page answers, Google PageSpeed Insights for the speed test, Resend for email and Cloudflare Turnstile for the bot check. The PDF is built in the browser with pdf-lib.

Can I try it before I talk to you about my own app?

Yes. The first scan of a domain is free for up to 100 pages at /website-scan. The $99 audit covers up to 3,000 pages, 3 competitors you name and one rescan.

Could you build something like this for my business?

If your app needs saved progress, payments, outside APIs or daily limits, those are the same parts. Custom builds are quoted after a free call, and you get a written scope with dates before anything starts.

Sources & further reading

YOUR NEXT MOVE

Make the next step count.

Bring your website and the questions you still need answered, and we’ll map the right next move.

Talk about your app

The first strategy call is free, and during business hours we aim to reply within five minutes.

From the businesses we've worked with

“Sales have been better the last few months thanks to some additional marketing efforts. Way to go, Jeff!”
Andrew MontiethGeorgetown Market · Jeff was its marketing director, 2023 to 2025
“Trojan Digital Marketing gave our website a complete make over. The board members and I are absolutely thrilled!”
C. Teague McClendonAmerican Veteran Service Corp
“I love it. I really do. I love that you put where you can find my skincare at (GTM), and I love the picture of the dripping aloe lol that’s a nice touch. Love the before and afters and the new tags, like 'our collection,' and the homepage. I love the way it scrolls better. Thank you so much, Jeff. I couldn't have done it without Trojan Digital Marketing.”
Alexa MahaberRedefined Body skincare