A vehicle listing is straightforward: a photo, a description, a price. The application becomes more interesting when someone offers a car for sale, staff need to assess it, and an enquiry needs a useful response.

Navarro connects those pieces for a FiveM roleplay dealership. It combines a storefront, a seller-intake flow, and an authenticated staff back office in one application.

At the time of the original write-up, the deployment ran within Cloudflare’s free-tier allowances, with no monthly infrastructure charge. That describes the project at that point in time—not a promise that another application, or a later version of this one, will cost nothing to run.

The useful story is how that constraint shaped the architecture, the use of AI, and the limits of the original implementation.

01 / ARCHITECTUREArchitecture: one deployment, three interfaces

One wrangler deploy ships the application. A Hono Worker handles API requests under /api and serves the static React 19 application for other routes.

That application has three surfaces: the public storefront at /, the staff console at /admin, and a mobile-app mock at /app. The last is an interface presented inside an iOS-style frame, not a shipped native application.

D1 is the shared store for inventory, submissions, sales, and enquiries. R2 holds vehicle and submission photos. The distinction is simple: structured records live in the database; photo files live in object storage.

INTERACTIVE FIGURE
One application. Clear responsibilities.

01 / Experiences

↓ API requests connect to the Worker

02 / Application

↓ Separate branches—not a pipeline

03 / Services & assets

Application layer

Hono / Worker

API requests are handled under /api. Other routes fall through to static React assets. D1, R2, and Workers AI have separate responsibilities—not consecutive stages in a pipeline.

Follow a branch:
Read all component descriptions
Public storefront
Vehicle inventory, customer enquiries, and a “sell us your car” intake flow. This is the public-facing side of the documented application.
Staff console
Individual staff accounts access inventory, valuations, enquiries, and audit history. It shares the application API with the storefront.
Mobile-app mock
The /app route is rendered in an iOS-style frame. The source describes a mock interface, not a shipped native mobile application.
Hono / Worker
API requests are handled under /api. Other routes fall through to static React assets. D1, R2, and Workers AI have separate responsibilities—not consecutive stages in a pipeline.
D1 database
Inventory, submissions, sales, enquiries, and cached AI results. This is structured application data, separate from the photo files stored in R2.
R2 storage
Vehicle and submission photos. In the original account, photos were served through the Worker without an image-resizing pipeline.
Workers AI
Appraisal notes, enquiry triage, and a scheduled business briefing. The model does not calculate buy ranges; those come from catalog-based rules.
Static assets
Non-API requests fall through to the ASSETS binding, which serves the static React application. This is a separate route from the data and AI services.
Click a component to inspect its role. Conceptual map of the July 2026 account, not a live request monitor or a complete network diagram.

There is also a small implementation detail that matters for testing. The export adds the scheduled handler to the Hono app rather than replacing the app with a plain wrapper object:

worker/index.ts / TypeScript
app.route('/api', api);
app.all('/api/*', (c) => c.json({ error: 'Not found' }, 404));
app.all('*', (c) => c.env.ASSETS.fetch(c.req.raw));

// Keep Hono's methods, including .request(), available to the tests.
export default Object.assign(app, {
  async scheduled(_event: ScheduledEvent, env: Env, ctx: ExecutionContext): Promise<void> {
    ctx.waitUntil(generateBriefing(env).then(() => undefined));
  },
});
Source excerpt · not executed in this design sample · scroll code horizontally on narrow screens

Excerpt from worker/index.ts, as reproduced in the supplied article; explanatory comment shortened.

Keeping Hono’s .request() method available lets the integration suite call the Worker directly. The scheduled handler regenerates the business briefing, which leads to the next design decision: generating an insight and reading it should not be the same operation.

02 / AI ON A BUDGETAI on a budget: generate separately, read from cache

Workers AI supports three parts of Navarro: short appraisal notes on seller submissions, enquiry triage with intent, sentiment, and suggested replies, and the daily business briefing.

The AI allowance in the original deployment shaped how those features worked. Results are cached in D1. The briefing regenerates once a day through the scheduled handler. A manual “Regenerate” action is restricted to the owner and rate-limited.

The dashboard reads the cached briefing instead of making a new model request whenever someone opens it.

This separates the staff member’s immediate task—reading the dashboard—from the background task of generating a fresh summary. It also means the briefing is a cached snapshot, not a live interpretation of every change.

ANIMATED EXPLANATION
Generate separately. Read from cache.

01 / Scheduled generation

Daily trigger
AI briefing
D1 cache

02 / Dashboard read

Open dashboard
Read cache
Show stored briefing

Choose a path to follow. Opening the dashboard does not trigger a fresh briefing generation.

Explanatory animation; no model is called. The read path assumes a cached result exists. A failed generation does not guarantee a fresh or available briefing.

03 / THE AI BOUNDARYThe valuation itself is not AI

RESPONSIBILITY EXPLORER
What does the model actually own?

DETERMINISTIC / CATALOG RULES

The rules set the range.

Catalog MSRP and tier-specific percentages produce the low and high buy range. The submitted price is then classified against that range—not priced by a model.

Buy range = defined calculation. Appraisal note, triage, and briefing = language assistance. No fictional valuation numbers or model output are shown.

A language model can help explain an appraisal. In this implementation, it does not calculate the buy range.

That range comes from the pricing catalog and tier-specific percentages. The submitted price is then compared with the calculated range:

worker/insights.ts / TypeScript
let buyRangeLow: number | null = null;
let buyRangeHigh: number | null = null;
let flag: ValuationFlag = 'no_catalog_match';
if (msrp && tier) {
  const [lowPct, highPct] = BUY_RANGE[tier] ?? BUY_RANGE.C;
  buyRangeLow = Math.round(msrp * lowPct);
  buyRangeHigh = Math.round(msrp * highPct);
  flag = sub.price < buyRangeLow ? 'below_range'
       : sub.price > buyRangeHigh ? 'above_range' : 'in_range';
}
Source excerpt · not executed in this design sample · scroll code horizontally on narrow screens

Excerpt from worker/insights.ts, as reproduced in the supplied article.

The code establishes the range and classification; the model supplies the narrative note. The distinction matters because a useful explanation and an authoritative calculation are different responsibilities.

AI writes the explanation.
Defined rules establish the numbers.THE BOUNDARY THAT MATTERS

Failure behavior is part of the same boundary. For enquiry triage, where the implementation has no sensible fallback, the endpoint returns a 503 rather than pretending that analysis succeeded.

04 / PUBLIC-LAUNCH HARDENINGHardening for public launch

The difficult work was not only adding features. Public forms and a staff console also introduce questions about identity, input, abuse, and accountability.

The implementation described in the original article addressed those questions with several layers.

Staff identity. Each staff member has an individual login, with HMAC-signed session cookies instead of a shared passcode.

Public endpoints. Login and public-form endpoints use Cloudflare’s rate limiting. Sell and enquiry forms use server-verified Turnstile when both required keys are configured.

Input boundaries. Public and admin text fields have length caps. Image keys are restricted, uploads have a 3 MB cap, and secureHeaders supplies response security headers.

Activity history. An append-only audit log records admin activity. That is an accountability mechanism, not a substitute for access control or evidence of an independently audited system.

These details are less visible than the storefront. They are still part of building an application intended to accept public input.

What the tests establish—and what they do not

READ THE TEST COUNT IN CONTEXT

API integration101 tests reported in the original account
Frontend behaviorMeaningful coverage remained a gap
Deployed protectionsConfiguration & enforcement need separate verification
A historical source claim—not a new test run, security score, or guarantee of whole-application coverage.

The original write-up reported 101 API integration tests running against the Worker in the Cloudflare Vitest pool, including local D1 and R2.

That count describes the suite at the time of writing. It is not a test run performed for this rewrite, and it should not be read as “the whole application is covered.” The frontend had little meaningful test coverage.

There is another important limit: rate limiting was represented by no-ops in local development and tests, while Turnstile remained inactive until both keys were configured. Passing those tests therefore did not, on its own, demonstrate that the deployed abuse protections were configured and enforcing their rules.

05 / THE NEXT ITERATIONWhat I would improve next

Test the interfaces people actually use

The API-side suite does not replace coverage for the storefront, submission forms, and staff console. Frontend testing was the clearest gap identified in the original article.

Add a photo-delivery pipeline

Photos were served through the Worker from R2 without a resizing pipeline. The next step identified in the write-up was to improve image delivery rather than keep serving source photos unchanged as the project grew.

Make admin state easier to manage

By the original write-up, the admin console spanned eight views and its client state had grown organically. More deliberate shared state management was a maintenance improvement still to make—not a solved problem hidden behind the finished interface.

06 / THE TAKEAWAYThe takeaway

Navarro’s engineering story is not simply that several services fit inside one deployment. It is the boundary between them.

The interface presents the work. The API connects it. The database stores records and cached results. Object storage holds photos. AI assists with language, while defined rules calculate valuations.

The next layer of quality comes from testing those boundaries, handling failure honestly, and being clear about what is still unfinished.

Sources, original date & scope of this sample

This page implements the supplied sample rewrite of “A FiveM dealership platform on Cloudflare’s free tier.” The original publication date is July 3, 2026; this sample is not a published revision.

  • Editorial source: MRoot-Sample-Page-Rewrites.md, supplied in this conversation.

  • Technical source: the complete Navarro article extracted from MRoot-Visual-Storytelling-Handoff.zip. Both code excerpts and original qualifications are retained.

  • Diagrams explain the documented account, not current application behavior independently verified here. No production code, security configuration, cost, or original test count was re-audited.

Open the original published article ↗