Some details are redacted. With an access key, you can see the full version.
A commercial van conversion specialist
A van conversion dealer site and its quote simulator
Replaced a Wix site with a SolidStart visitor site and a Nuxt CMS, adding a quote simulator that spans 114 price points and cutting first paint from over three seconds to under one.
Overview
A rebuild of the website for a dealer that converts Toyota HiAce and Nissan Caravan vans. The old site ran on Wix, took over three seconds to first paint, and offered no way to produce a quote online. I handled the new one from design through launch and brought first paint under one second.
The stack splits along the two audiences: SolidStart with server-side rendering for visitors, a Nuxt CMS for staff. Both run on Cloudflare Workers and share one D1 database. It is a single Turborepo monorepo, with the quoting logic extracted into its own package that both sides consume.
The core feature: a conversion quote simulator
The simulator covers 13 manufacturer body specifications and 23 conversion plans, which cross into 114 quotable price points, each carrying the options that actually apply to it. The 49 options are organised into 8 groups, single-select and multi-select groups mean different things, and the combinations come to roughly 8.6×10¹⁰.
All of it is configuration in D1, maintained through CMS forms. Staff can change models, prices, options and what applies where, but they never see JSON and never write a rule expression. When a new kind of relationship is needed, I add one to the engine and pair it with a form written in the language of the business.
Architecture
Three Workers share one D1 database. The visitor site is read-mostly, with only two write paths: enquiry submissions and the "interested" counter. The CMS does all the reading and writing, and a cron Worker that pulls from YouTube and Instagram runs every six hours.
The cron Worker is the only process that touches third-party APIs, and the third-party credentials live on it alone — neither the visitor site nor the CMS can reach them. Fetched results are written to D1 as a snapshot; on failure it records the error and leaves the previous data alone, so the homepage keeps showing the last successful pull.
The hard parts
Cascading invalidation, iterated to a fixed point
Removing one option invalidates the options that depended on it, and those invalidations can take out another layer below them. The rule engine sweeps the selected set in a while-changed loop and stops only when a full pass changes nothing; a single pass would leave dangling selections behind.
The engine is a pure-function package with zero runtime dependencies — it knows nothing about the DOM, the framework, or the database. That is what lets the visitor site and the CMS preview share one set of decisions, and it makes the engine directly unit-testable. Everything that touches money is extracted the same way.
Dictionary encoding on the wire
A nested payload embeds the option catalogue once per price point, and with 2,837 applicability rules across 114 price points the same option definitions were serialised over and over. The server now sends a top-level dictionary plus id references, and the client rehydrates once. Neither the engine nor the UI had to change.
Catching dependency cycles at save time
If staff configure A to require B while B requires A, both options become permanently unselectable on the public site. Before writing to the database, the CMS runs a three-colour depth-first search and refuses the save when it finds a cycle, translating the id path back into option names for the person editing.
The same check stops two other dead configurations: a dependency target inside the same single-select group, and a dependency target that is not among the options applicable to that price point. Either one produces an option that visitors can see but can never click.
Other decisions
- Quotes are never persisted. A shared link carries only the price-point id and option ids; the server recomputes from D1 and runs the rule engine again, so hand-editing the URL cannot assemble an illegal combination or inflate the total
- Options whose conditions are unmet stay visible but disabled, labelled "requires ◯◯" or "cannot be combined with ◯◯", so customers can see how the models differ
- The quote PDF is generated in the browser; jsPDF is imported dynamically on click, so it stays out of the SSR output and the main bundle
- Quote configuration is read-heavy, so server results sit in the edge cache with a five-minute TTL
- Anything involving D1 is tested on the real workerd runtime against a database built from the real migrations — the database is never mocked

