Stack
Two stacks, chosen for different reasons: the Actors optimise for not breaking, and this site optimises for being read by machines as well as people.
The Actors
Python on the Apify platform, with Crawlee for the crawling layer and Playwright available but deliberately unused where the target renders server-side. Both current Actors run with no browser at all, which is why a coches.net run is measured in cents rather than dollars: one HTTP request returns 35 fully populated listings.
Concurrency is capped low on purpose. Measured against Nextdoor, five concurrent requests ran clean while ten triggered a roughly four-minute cooldown — so the cap is not politeness at the expense of speed, it is the faster configuration.
Output goes to named datasets with declared views, so one set of rows can be read as a businesses table or a recommendations table without duplicating storage. Failures go to a separate errors dataset with the reason attached, and they are never charged.
This site
Next.js 16 with the App Router, React 19, TypeScript in strict mode and Tailwind CSS v4, exported as static HTML. There is no server: output: "export" renders every page at build time and the result is served from S3 behind CloudFront.
That is a content decision, not an infrastructure one. The served HTML is the product — it is what Google, the generative engines and a developer on a bad connection actually receive — so nothing important is allowed to depend on JavaScript running. Server Components are the default and there are four client components on the whole site: the theme toggle, the mobile menu and the two table-of-contents variants, each of which only adds a highlight or an overlay to markup that already works without it.
The FAQ blocks are native <details> elements rather than a stateful accordion, for the same reason: a page that emits FAQPage structured data has to have the answers in its HTML, or the markup is describing something the reader cannot see.
Delivery
A CloudFront Function handles the two things S3 cannot: canonicalising the host and the trailing slash with a 301 so each page has exactly one indexable URL, and mapping the clean URL to the .html object the export actually produced.
Deployment is a script rather than a click, and it refuses to publish on its own findings: it aborts if the build contains a localhost URL, if a canonical does not match its own page, if a title is duplicated, if an internal link is broken, or if an FAQPage answer is missing from the page it describes. Assets carry immutable cache headers because their names are content-hashed; HTML revalidates every time.
What is deliberately absent
- No component library. About fifteen primitives, all local. The accordion is a
<details>and the mobile menu is a<dialog>, so the content is in the served HTML and the accessibility comes from the browser. - No syntax highlighting. It needs either a client-side library on every page or a build-time tokeniser that triples the HTML, and it buys nothing for somebody about to paste a JSON input into a form.
- No analytics that blocks rendering, and no cookie banner, because there is nothing to consent to.
- No MDX. The article bodies use the same components as the rest of the site, and a markdown pipeline would need a mapping layer kept in sync with the component API anyway.