This month I started site-uri.ro, a small web studio in Romania that builds presentation sites and online stores for small businesses. Before showing anyone a portfolio, the studio's own site had to be the first proof: fast on a phone, easy to find, and boring to maintain.
Here is how it's built, with the parts of the code that matter.
Static output, one canonical URL per page
The whole site is Astro, built to plain HTML:
// astro.config.mjs
export default defineConfig({
site: "https://site-uri.ro",
trailingSlash: "never",
build: { format: "file" },
integrations: [sitemap({ filter: (p) => !p.endsWith("/404") })],
});
format: "file" writes /preturi.html, which is served at /preturi with no trailing slash. Every page has exactly one URL: the one in the sitemap, the canonical tag and the internal links. No redirect chains, nothing for Google to deduplicate.
No JavaScript framework ships to the browser. The pages carry three small inline scripts (the homepage animation, the mobile menu and the contact form) and zero .js files.
One Worker route, everything else is a file
The site runs on Cloudflare Workers with static assets. The Worker script only wakes up for one path:
// wrangler.jsonc
"main": "worker/index.js",
"assets": {
"directory": "./dist",
"binding": "ASSETS",
"run_worker_first": ["/trimite"],
"not_found_handling": "404-page"
}
/trimite is the quote form. Every other request is a static file served directly, without running any code.
A contact form with no database
The form posts JSON to the Worker, which sends it as an email through Cloudflare Email Routing (send_email binding) and stores nothing. Spam protection is two cheap traps instead of a CAPTCHA:
// From worker/index.js (comments translated)
// A hidden field a human never fills, and a form submitted less than
// 3 seconds after the page opened. Bots get a fake "ok" so they don't retry.
const openFor = Date.now() - Number(data.deschis ?? 0);
if (String(data.site_web ?? "") !== "" || !(openFor > 3000)) return reply(200, "ok");
The Worker also checks the Origin header, caps every field's length, and sets Reply-To to the visitor's email when they leave one, so answering a request is just pressing Reply.
Prices that can't go stale
Prices live in one TypeScript file. Pages never write a price by hand; they call a helper with the package name:
// Names translated from the Romanian original
export function price(s: Service, name: string): number {
const p = s.packages.find((p) => p.name === name);
if (!p) throw new Error(`Package "${name}" does not exist in ${s.slug}`);
return p.price;
}
A typo in a package name, or a "from X" price that doesn't match any real package, stops the build. A title, a meta description or an FAQ answer can't keep advertising an old price after the price list changes.
The same idea applies to structured data: breadcrumbs (BreadcrumbList), the business (ProfessionalService) and the founder (Person, with sameAs links) are generated from the same data files the pages read.
The numbers
- Homepage on a phone: 199 KB in total, including fonts and images (what a 375 px, 2x screen downloads).
- 0 JavaScript files; 3 short inline scripts.
- 0 accessibility errors from axe-core at the last check (27 October 2026).
- Security headers (
HSTS,nosniff,Referrer-Policy,Permissions-Policy) in a_headersfile.
The portfolio problem
A new studio has no clients, so the portfolio starts with six demonstration projects, each built end to end as if for a real client: a plumber, a dental clinic, a linen clothing shop, a guesthouse, a pottery studio and a real estate agency. For each one I first studied the top sites in that niche (what people search for, what information is missing, which structure works), then designed it with its own identity. They're clearly labelled as demos on the site.
You can see them at site-uri.ro/portofoliu.
Next up: the first real projects, and a write-up of how the real estate demo keeps its filters in the URL with about 2 KB of JavaScript.
If you build sites for small businesses: what do you ship by default, and what do you leave out?
Top comments (0)