LyfWis.com - a studio site running its own CMS on the edge, built end to end

The problem

Most studio websites are one of two things. Either a WordPress theme, which means plugins, patches, and a site that gets slower every year, or a page builder, which is fast to launch but rents you your own website forever. Stop paying and it disappears.

We spend a lot of time telling clients to own their code and keep their stack small. It would have been strange to publish that argument on a site that ignored it.

There was a practical constraint too. This had to be maintainable by one person alongside client work. That rules out anything with a build pipeline to babysit, a framework to keep current, or a hundred packages that each need watching. The biggest challenge wasn't "can I build this?" It was "will I still want to touch this in two years?"

How we approached it

There is no framework, and no build step, the site you are reading is a single JavaScript file and a folder of static assets which handles all: the pages, blog, admin panel, forms, media, sitemap.

The admin panel manages leads, blog posts, case studies, client reviews, products, the about page and newsletter subscribers. Authentication is an identity check rather than a password in a database, with an IP allowlist behind it as a second layer.

Public forms get Turnstile, a honeypot field and KV-backed rate limiting. Every database query is parameterised. The Content-Security-Policy is strict, and the admin panel runs under a separate, stricter one.

A few decisions that only look small until you skip them. The sitemap is generated from the database on request, so publishing a post puts it in the sitemap immediately, therefore, no file to edit, no deploy. Navigation is defined once and injected into the static pages at the edge by a streaming HTML rewriter, so it cannot drift between templates. Structured data is emitted per page type so search engines and AI crawlers get an accurate picture rather than guessing.

Data handling got the same treatment as the code. IP addresses are truncated at the point of collection rather than stored in full. Enquiries are deleted automatically after 24 months by a scheduled job and the retention period in the privacy policy is enforced in software, not left to memory.

Near-perfect Google PageSpeed (Performance, Accessibility, Best Practices, and SEO all at or near 100), fully self-managed with no monthly platform fees, and hardened in a dedicated security review - fast, accessible, secure, and entirely ours to run.

Lighthouse [97] mobile · [0.4]s LCP · [100] desktop · $0/month hosting · zero dependencies

What the visitor sees

LyfWis Hero on Desktop
LyfWis Hero on Desktop
LyfWis Contact Form on Desktop
LyfWis Contact Form on Desktop
LyfWis Pricing on Desktop
LyfWis Pricing on Desktop
LyfWis Footer on Desktop
LyfWis Footer on Desktop

What the owner sees

LyfWis Admin Panel Work Tab on Desktop
LyfWis Admin Panel Work Tab on Desktop
LyfWis Admin Panel Blog Tab on Desktop
LyfWis Admin Panel Blog Tab on Desktop
LyfWis Hero on Mobile
LyfWis Hero on Mobile
LyfWis Pricing on Mobile
LyfWis Pricing on Mobile
Google Pagespeed Insights Mobile
Google Pagespeed Insights Mobile
Google Pagespeed Insights Desktop
Google Pagespeed Insights Desktop

Built with

JavaScript (ES2022)HTML5CSS3SQLREST APIsJSONServerless edgeZero dependenciesNo build step

What I'd do differently

Three things, all of which cost me time later.

The navigation started as markup copy-pasted into six files. It drifted, exactly as you would expect. It is now defined once and injected at the edge, which is where it should have started. Any content that appears on every page needs a single source from day one, not once you notice the copies disagreeing.

The sitemap was a static XML file. Blog posts live in the database, so the file could not possibly know about them, therefore, every publish needed a manual edit I had to remember. Obvious in hindsight. It is now generated from the database on request and cannot fall behind.

The one that actually cost something: I turned on "Block AI training crawlers" rule, assuming it separated training crawlers from search crawlers. It does not, for Google, Google-Extended is a robots.txt signal riding on Googlebot's own infrastructure, so blocking it blocks Googlebot. My sitemap silently stopped being readable and crawl discovery fell to six per cent before I found it in the security logs. The fix took ten minutes; noticing took considerably longer. Security rules that match on crawler identity need verifying against real traffic, not assumed to behave as labelled.

Visit the live project →