Product Decisions
Why we replaced a SaaS starter with a static-first studio site
Why we kept the useful parts of the old site's public footprint, dropped its SaaS runtime assumptions, and proved the replacement on isolated Cloudflare staging.
A starter can save weeks when the product needs what the starter already assumes.
It can also make a small website carry the architecture of a much larger product.
That was the problem here.
The website we actually need
The V1 studio site has a small job:
- explain the studio clearly;
- publish real projects and build notes;
- support English and Simplified Chinese;
- be easy for search engines and AI crawlers to read;
- provide a low-friction path to start a project.
It does not currently need user accounts, subscriptions, credits, an admin dashboard, RBAC, or a database-backed application runtime.
The previous template lineage included assumptions like those because it was designed to launch SaaS products quickly. Those are valuable features in the right product. They are just not the job of this site.
What the old site told us
Before writing new code, we froze the public evidence instead of guessing from an old repository.
The production site still exposed a Next.js/OpenNext runtime. /blog and /showcases returned 200 but contained template-oriented content. The sitemap pointed at https://your-domain.com/. Pages emitted more than one canonical URL.
A random nonexistent URL did return a true 404, so not everything was wrong. That mattered too: migration evidence should preserve facts, not just defects.
The decision
We kept two things separate:
Business migration means preserving real URLs, useful content, possible backlinks, and redirect equity where they exist.
Runtime migration means preserving the old framework, dependencies, and deployment shape.
The first can be valuable. The second needs evidence.
For this site, there was no evidence that preserving the old runtime created value. So the new baseline became:
Astro static-first + Markdown/MDX + Cloudflare Workers Static Assets.
No compatibility layer. No default SSR. No database waiting for a future feature.
The smallest useful validation
We did not attach the new build to the production domain.
Instead, the validation path was:
- create a clean repository;
- implement the real bilingual URL structure;
- generate static HTML;
- deploy to a separate
workers.devstaging hostname; - test the parts that are easy to get subtly wrong.
The live acceptance checked:
- 12 core pages returning 200;
- a true 404 for a missing URL;
- 307 normalization to the canonical trailing-slash URL;
- one canonical per page;
- reciprocal English / Chinese hreflang plus x-default;
- noindex and robots blocking on staging;
- sitemap using the real staging hostname;
- raw HTML containing the primary content;
- Googlebot, Bingbot, OAI-SearchBot, and a normal browser UA receiving 200.
We also measured the rendered page with real Chromium device metrics at 390px and 1440px to verify there was no horizontal overflow.
A useful testing mistake
The first mobile screenshot looked broken even after the CSS fix. The header menu was missing and the right side of the headline appeared clipped.
The problem was not the page. macOS Chrome headless was enforcing a minimum layout window larger than the requested 390px while the screenshot itself was narrower.
We switched from --window-size=390 to Chrome DevTools Protocol device emulation and measured the actual values:
innerWidth = 390document.scrollWidth = 390
That distinction matters beyond CSS. A screenshot is not evidence if the test environment is lying about the viewport.
What the decision survived
The most useful test of an architecture decision is not the day it is made. It is what happens when the product grows.
After the first static foundation passed staging acceptance, the site added a reversible scroll-driven homepage, richer bilingual content, and the first three Project evidence records. Those changes increased interaction and content complexity, but they did not create a reason to add SSR, a database, authentication, or a heavier application runtime.
That is stronger evidence than the original framework comparison. The architecture stayed small while the actual product surface grew.
What this changed
The remaining uncertainty stopped being whether the public site needed a more capable runtime.
At that pre-release point, the next decisions moved to product and release boundaries instead: how much evidence is enough to earn trust, how little friction a prospective client needs to start a conversation, how English and Chinese can stay native while sharing the same facts, and how to replace a legacy production binding with a bounded rollback window and evidence-based redirects.
Those were more valuable questions than adding framework capability to a site that had not asked for it. The subsequent production cutover passed without changing that architecture: the apex moved to the dedicated Static Assets Worker, while the old Worker remained intact only through the cutover and early post-launch acceptance window. Once the new production path was established, that unused Worker and its dedicated D1 database were retired.