A firm doing mechanical contracting and authorised product sales. Two positions: project contracting on one side (thermal plants, ventilation, automation), Siemens product groups on the other.
Two positions × nine services × six product groups × three languages. The page count reached ninety.
Two options
One: install a CMS and enter the content in a panel. Sounds reasonable. But this site’s content changes not monthly but yearly. A CMS means maintenance, upgrades, plugins and a security surface; in exchange you get a panel nobody will use.
Two: write the content as data and generate the pages. I chose that.
src/content/services.mjs ← service copy, three languages
src/content/siemens.mjs ← product groups
src/content/site.mjs ← company details, contact, legal
src/i18n.mjs ← interface strings
src/routes.mjs ← which language sits at which path
src/layout.mjs ← one header, one footer
build.mjs
↓
dist/ 90 pages + sitemap + robots + llms.txt
What “one source” means
The phone number is written once. In the footer of ninety pages, on the contact page, in the structured data and in the messaging link, it comes from the same variable.
Change the number and one line changes, and ninety pages are correct.
On a hand-written site that does not happen: the number is updated in the footer, forgotten on the contact page, stale in the schema. Six months later a search engine shows a number that does not belong to the company, and nobody knows why.
Address, phone and legal name. Search engines use these as entity signals; written differently in different places, the company looks like two businesses and both are weakened.
Single-sourcing is not a tidiness obsession — it is a visibility issue.
The part of three languages that breaks most
The most common mistake I see on multilingual sites is hreflang. Usually it is written as: the Turkish page points at English, the English page at Turkish, and German points at nobody.
The correct form: every page must list all its siblings, including itself. With three languages that is four lines per page.
<link rel="alternate" hreflang="tr" href="…/hizmetler/isitma-sistemleri/">
<link rel="alternate" hreflang="en" href="…/en/services/heating-systems/">
<link rel="alternate" hreflang="de" href="…/de/leistungen/heizungsanlagen/">
<link rel="alternate" hreflang="x-default" href="…/hizmetler/isitma-sistemleri/">
Note that the paths are translated. The German page sits at /de/leistungen/heizungsanlagen/. That is both readability for the user and a language signal for the engine.
Writing that by hand across ninety pages is impossible. For a generator it is trivial — it derives it from the routing table.
The generated folder goes into version control
Another choice: the generated dist/ folder lives in git.
Most projects do not version build output, rightly. But here the folder is exactly what goes to the server. Keeping it in git has three benefits: you see precisely what shipped; no Node runs on the server, rsync just copies a folder; and if something breaks, deploying the previous commit is enough.
And the content side
The technical layer can produce ninety pages but cannot fill them. That is where the real work is.
For a service page to be useful, “we provide quality service” is not enough. Which equipment, which capacity range, which standard, which sector — these are what the customer searches for and what a search engine understands. And all three languages need the same level of specificity; the English cannot be an abridged Turkish.
What I learned
“Generated site” sounds too technical at first. What actually gets simpler is maintenance.
A year from now, whoever wants to add a service will not open a panel and fill in fifteen fields. They will add an object to a file and run the generator. The page, its place in the menu, its three translations, its sitemap entry and its schema will appear on their own.
What makes a ninety-page site manageable is not a panel. It is removing the repetition.