Skip to content
← Journal
2 min read

Wrapping a public site around a gated app

A note on the original shared build: prerender public pages while keeping the product client-only. The personal site now has its own repository.

Update, 15 October 2026: Livastar Observer is now my personal site in its own repository. Archetype Observer is a separate product. The notes below describe the earlier shared-build approach and the decisions behind it.

The original application started as one thing: a private, auth-gated product where every route except login redirected you to a login screen. No public page, no content, no SEO story. The initial goal was to add public portfolio pages around it without rewriting the product or splitting the stack.

Two shells, one build

The move was a routing split, not a rewrite. A public shell serves the home, projects, journal, and the rest. The existing product shell stays exactly as it was, now namespaced under /app/* and still behind auth. Providers for auth and product state wrap only the product group, so the public pages have no dependency on any of it.

Prerender the public, keep the app client-only

The public routes are statically prerendered to real HTML at build time, so a crawler sees a full page and the first paint is fast. The gated routes render client-only: their prerendered HTML is a clean neutral shell with no product UI baked in, and the real app hydrates on top.

The nice property is that neither side leaks into the other. The public site never imports the product's store or its data layer, and the product never carries the public site's content pipeline.

What made it cheap

Staying on the same React and Vite stack. Adding prerendering as a build concern rather than a framework migration. And keeping content as version-controlled MDX in the repo, so publishing is a pull request, not a CMS.