← Blog

our website and our app were two products. now they are one.

You could tell where you were on our product by looking at the bottom of the page. The marketing site had a five-column footer; the dashboard had a single row of links. Different navbars, different spacing, different fonts in places. Two teams' worth of divergence, produced by one person, because they were two codebases.

They are now one Aurelia application. The home page, the catalogue, the blog, this knowledge base and the dashboard are routes in the same app, sharing the same navbar, footer, tokens and components.

the part that is actually hard

A single-page app is invisible to anything that does not run JavaScript. Moving a marketing site into one is a good way to lose every search ranking you have.

So the build prerenders. It boots the real application in a headless DOM, navigates to each public route, waits for it to settle, and writes the resulting document. A crawler gets complete HTML — title, description, canonical, structured data — produced by the same code the browser runs. There is no second implementation of the pages to drift.

The prerenderer also refuses to ship bad pages. A missing title, a description under forty characters, or anything other than exactly one <h1> fails the build. That check caught four legal pages rendering two headings each, within a minute of writing it.

two bugs we would not have found by reasoning

The canonical URL redirected. Writing pages as models/index.html made nginx 301 /models to /models/ — so every URL in our sitemap bounced before it answered. Pages are now written as models.html and served by try_files $uri $uri.html, at exactly the address we advertise.

The fallback served the wrong page. Client-only routes fell back to index.html, which after prerendering is the home page. So /login showed the marketing hero until the router caught up, and a crawler reaching a private URL would have indexed the home page under it. The fallback is now a neutral shell.

Both were found by requesting the pages and reading what came back. Neither would have shown up in a green build.

what you get out of it

One design, honestly. The theme switcher moved to the footer where a preference belongs; the language switcher took its place in the bar. Every data table collapses into readable cards on a phone instead of scrolling sideways. And every page — marketing or product — inherits a fix the moment we make it, because there is one component to fix.

How the whole system works · Knowledge base · Read our code

Read next

one model, every card: how a 27b runs on 8 gb

Qwen3.8 27B ships in five builds, from Q5_K_M on a 24 GB card down to IQ2_XXS on 8 GB. Why we split models from builds, what quantization actually costs, and how a quantized KV cache doubled our context overnight.

we stopped projecting your earnings and started measuring them

Most GPU marketplaces advertise what a card could earn. Our dashboard shows what yours measured, and refuses to show a number until it has one. Here is why that choice cost us a nicer-looking page and was still right.

Model catalogue · Knowledge base · Run a GPU