How We Built 280 Sample Business Websites with Laravel and Claude
People who run a small business rarely want to read about a website builder. They want to see a website for their own trade, a plumber's site if they are a plumber, a bakery's site if they bake, and then decide. So we bu
People who run a small business rarely want to read about a website builder. They want to see a website for their own trade, a plumber's site if they are a plumber, a bakery's site if they bake, and then decide. So we built 280 of them, one per trade, each a complete working site for an invented business, and put them all on one page.
This post covers how the pipeline works, why we never let the model write HTML, and what 280 trades taught us about what a small business website actually needs.
The problem with letting a model write the page
Asking a model for a finished HTML page works often enough to be exciting and fails often enough to be useless. One page drops the contact form, the next invents a CSS class that does not exist, a third quietly changes the layout because the prose ran long.
The fix was to stop asking for pages and start asking for data.
Templates with a slot schema
Every template in our library (there are now over 180, across 10 layout families) ships with a slots.schema.json file. The schema says which pieces of content the page needs and how big each one may be. A simplified hero section looks like this:
{
"hero.headline": { "type": "string", "maxLength": 80, "description": "Bold first impression" },
"hero.subheadline": { "type": "string", "maxLength": 160, "description": "What the business does and for whom" },
"hero.cta_label": { "type": "string", "maxLength": 28, "description": "Button text" },
"services": { "type": "array", "items": { "name": "string", "price": "string", "blurb": "string" } }
}
The model's only job is to fill those slots for one business. Laravel validates the result against the schema, and a Mustache renderer drops the values into the template. Colors come from a separate palette file, so the model never touches CSS either.
That split gives us three things we could not get from free HTML:
- Every page is structurally valid. The template owns the markup, so a contact form cannot disappear.
- Editing is safe. When an owner later asks the AI to rewrite a section, we send only that section's slot values and merge back only string fields that already existed. The model cannot add keys or reshape arrays, so an edit can never break the layout.
- Templates are testable. Each template passes a validator of 13 checks before it ships, including a render simulation with default data, so failures show up at install time instead of on a customer's homepage.
Two pages per sample, one hub
Each of the 280 samples lives at two addresses:
- an explainer page, such as
/sample-websites/plumber-website, which describes what is on the site and why it is there - the live site itself, at the same address with
-sampleon the end, which works like a real business site with its own pages, forms and, for retailers, a shop
All 280 explainer pages are listed on a single hub, grouped by sector, with ordinary links and no pagination: sample websites for 280 kinds of business. Keeping everything on one page was a deliberate choice. It keeps every sample one click from the hub and it means a crawler never has to run JavaScript to find them. Each sample also has a plain markdown copy at the same path with .md on the end, which is cheap to serve and easy for machines to read.
What 280 trades taught us
Generating the same kind of site 280 times exposes patterns that you would never notice building five. The content a trade needs depends far more on the customer's situation than on the industry label.
Emergency trades lead with reach. Locksmiths, towing, plumbers and water damage firms get visitors who are standing in a problem. Their pages put the phone number in the first screen, say how fast someone can arrive, and explain how the price is set before anyone touches the lock. Look at the plumbing sample: repairs are listed by the symptom people search for, not by the plumber's own job names.
Considered purchases lead with process. Kitchen remodelers, solar installers and home renovation firms sell something expensive that does not exist yet. Their visitors want to know what happens after they ask for a quote: the site visit, the proposal, how long the work takes and what living with it is like.
Retail needs options before persuasion. Shoe shops need size choices, furniture stores need fabric and finish, cosmetics need shades. A product without its options is a product people cannot buy online.
Hospitality sells the room and the table. Hotels, restaurants and venues need to show each room or menu honestly and take a booking or enquiry without a phone call.
None of this is new to anyone who has run one of these businesses. The useful part is where that knowledge lives. If it sits in a prompt, the model may or may not act on it. If it sits in the template as a slot, the model has to fill it every time, so the template decides what must be said and the model only decides how to say it.
What we would do differently
If you are building anything that generates pages with a language model, three lessons carried over from this project:
- Give the model a form, not a canvas. Structured output with length limits beats clever prompting every time.
- Make the template the source of truth for structure, and validate before anything is rendered.
- Build the examples your users would ask for. Showing a bakery owner a bakery site did more for us than any feature list.
If you want to see what the pipeline produces across trades, the full collection of sample websites is open to browse, and every sample is a live site you can click through. Questions about the slot schema or the validator are welcome in the comments.
Written with AI assistance and reviewed by the author, who built the platform described.
Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.