Founder review

Foundation Slice Review: Next.js 16 + Firebase App Hosting For An MDX-Only Public Surface

WorkedBy JakPublished

Setup context

Stand up an editorial public surface that can host founder reviews and process notes without exposing private workspace data.

Requirements

  • Editorial MDX hosting with build-time schema validation.
  • Server-rendered marketing/home with a single primary CTA and no modal pop-ups.
  • Five-layer workspace isolation that survives a deploy-platform swap.
  • British English document language declared on every page.

Resolved gear

  • Next.js 16 (App Router, output: standalone) Server runtime and route surface; standalone output keeps the runtime portable across Cloud Run, Firebase App Hosting, and bare VMs.
  • Firebase App Hosting Cloud Build + Cloud Run + Cloud CDN deploy target inside the existing borderkit-app project.
  • @next/mdx with build-time zod validation Editorial pipeline: schema-validated frontmatter, no Firestore IAM required.
  • @borderkit/tokens (workspace package) Shared design tokens consumed as Tailwind theme fragments; no React component coupling between workspace and public-web.
  • Bundle-leak grep Artefact-time deny-list scan against .next/ to prove private workspace symbols never reach the public bundle.

type: founder-review title: 'Foundation Slice Review: Next.js 16 + Firebase App Hosting For An MDX-Only Public Surface' slug: foundation-launch-review published: '2026-05-10' revised: null revision_log: [] status: published author: name: Jak role: founder setup_context: intended_use: 'Stand up an editorial public surface that can host founder reviews and process notes without exposing private workspace data.' environment: 'BorderKit monorepo (npm workspaces + Turborepo); Firebase project borderkit-app; UK-based founder.' conditions: 'Spec 1 foundation slice; tight coupling guarantees required at code, IAM, artefact, and runtime layers; multi-sprint authoring window.' constraints:

  • 'No Firestore IAM for the public-web service account.'
  • 'No firebase-admin or workspace DTO leakage in the public-web bundle.'
  • 'Portable Cloud Run exit-door via maintained Dockerfile.'
  • 'WCAG 2.2 AA from first deploy.' requirements:
  • 'Editorial MDX hosting with build-time schema validation.'
  • 'Server-rendered marketing/home with a single primary CTA and no modal pop-ups.'
  • 'Five-layer workspace isolation that survives a deploy-platform swap.'
  • 'British English document language declared on every page.' resolved_gear:
  • name: 'Next.js 16 (App Router, output: standalone)' role: 'Server runtime and route surface; standalone output keeps the runtime portable across Cloud Run, Firebase App Hosting, and bare VMs.' affiliate_disclosure: null
  • name: 'Firebase App Hosting' role: 'Cloud Build + Cloud Run + Cloud CDN deploy target inside the existing borderkit-app project.' affiliate_disclosure: null
  • name: '@next/mdx with build-time zod validation' role: 'Editorial pipeline: schema-validated frontmatter, no Firestore IAM required.' affiliate_disclosure: null
  • name: '@borderkit/tokens (workspace package)' role: 'Shared design tokens consumed as Tailwind theme fragments; no React component coupling between workspace and public-web.' affiliate_disclosure: null
  • name: 'Bundle-leak grep' role: 'Artefact-time deny-list scan against .next/ to prove private workspace symbols never reach the public bundle.' affiliate_disclosure: null review_outcome: worked refinements:
  • 'Lock the boundary deny-list next to the workspace DTO file so additions are caught.'
  • 'Keep the Dockerfile buildable from day one so the Firebase App Hosting exit door is testable.'
  • 'Render MDX through compiled modules, not raw text — discoverability of the schema-validation gate matters at edit time.' provenance: used_during: '2026-05' conditions_disclosed: true seo: description: 'Founder review of the BorderKit Spec 1 foundation: Next.js 16 on Firebase App Hosting with an MDX-only public surface and a real public/private boundary.' og_image: null canonical_url: 'https://borderkit.app/reviews/foundation-launch-review'

Setup context

The foundation slice had one job: get an editorial public surface live without giving it any way to read or write the workspace's private data. The workspace product holds gear catalogues, kit assemblies, event records, and notes that include exact storage locations, medical context, and schedule patterns. None of that is publishable. The public web exists to publish founder-authored editorial — reviews and process notes — and to host a waitlist field once a compliant processor is selected. The line between the two surfaces has to hold not by convention but by enforced absence: absence of IAM, absence of admin SDKs in the public bundle, absence of runtime calls that could pull workspace data.

The slice was authored across a multi-sprint window inside the borderkit/app monorepo. The constraints were inherited from Spec 1: no Firestore IAM on the public-web service account, no firebase-admin dependency in apps/public-web, a maintained Dockerfile so a portable Cloud Run exit-door exists from day one, and a WCAG 2.2 AA floor from the first deploy rather than as a later quality pass.

Requirements

The four binding requirements were: an editorial MDX hosting pipeline with build-time schema validation, a server-rendered marketing page with a single primary CTA and no modal pop-ups, a five-layer workspace isolation guarantee (code, IAM, artefact, runtime, bundle) that survives a deploy-platform swap, and a British English document language declared on every page. The requirements were the test surface for every tool decision below.

Resolved gear

Next.js 16 with the App Router was the runtime. output: 'standalone' was the deliberate part — it produces a portable Node runtime that runs on Cloud Run, Firebase App Hosting, or anywhere else that can host a Node binary. The standalone build is what makes the platform commitment reversible.

Firebase App Hosting was the deploy target, chosen because it sits inside the existing borderkit-app Firebase project (no new IAM root, no new billing line) and because under the hood it composes Cloud Build, Cloud Run, and Cloud CDN — all primitives we already understand. The exit-door property held: the same standalone build can be lifted out to plain Cloud Run via the maintained Dockerfile if Firebase App Hosting stops earning its keep.

@next/mdx with a zod-backed build-time validator was the editorial spine. The validator reads frontmatter via gray-matter, runs it through schemas in lib/content-schema.ts, and fails the build on any breach. This is the critical design decision: editorial validation lives at build time, not at runtime, which means it requires no Firestore IAM at all. The whole public surface is compiled artefacts.

@borderkit/tokens is the shared design tokens workspace package. Importantly, it ships only tokens — no React components. Tokens compile to Tailwind theme fragments that both apps/workspace and apps/public-web consume independently. There is no @borderkit/ui package; if there were, the public-web bundle would inherit workspace components and the bundle-leak test would have something real to catch. Keeping tokens-only is the discipline that makes the boundary cheap.

The bundle-leak grep is the artefact-layer check that closes the boundary. After every public-web build, an artefact-time scan reads .next/ and fails if any private workspace symbol (a DTO name, an admin SDK import) is present. The deny-list lives next to the workspace DTO file it guards, so new DTOs get added to the list as they are defined, not as an afterthought when someone notices a leak.

Review outcome — worked

The slice met its requirements. The marketing/home page renders server-side with a single primary CTA component reading from content/site-config.mdx, the editorial routes render MDX through compiled modules with the schema-validation gate active, the bundle-leak grep proves no workspace DTO leaks the boundary, and the Dockerfile builds and runs the same standalone output locally. The <html lang="en-GB"> attribute is set on the root layout. The five isolation layers — code, IAM, artefact, runtime, bundle — are each enforced by a distinct mechanism.

The "worked" outcome is qualified. The Spec 1 slice does not yet have the waitlist field live (vendor selection is constrained to a UK GDPR Article 28 processor; the deadline is fixed but the pick is not made), and the analytics posture is "no analytics" rather than "cookieless analytics" until that vendor is selected too. Both are deliberate "fallback to nothing" states per the addendum's §8 deadline fallbacks. The slice ships as an editorial-only surface with a secondary affordance ("Read founder reviews") as the primary CTA until the waitlist processor lands.

Refinements

Three refinements are going into the next slice rather than this one.

First, the bundle-leak deny-list belongs next to the workspace DTO file it guards. The first version of the deny-list lived in a separate config file and drifted within a single sprint when a new DTO was added without the list being updated. Moving the list adjacent to the DTO export makes additions visible in the same diff.

Second, the Dockerfile has to be buildable from day one, not as a later quality task. The exit-door property of Firebase App Hosting is only real if the standalone output is regularly exercised in a Cloud Run-shaped container. A Dockerfile that exists but is never built will rot. The fix is to run the Docker build in CI on every public-web change.

Third, render MDX through compiled modules rather than raw text. The schema-validation gate is much more discoverable when authors see their edits surface a compile error than when they have to remember to run a separate validator script. Compiling MDX into the route bundle was a slightly more involved setup; it paid for itself the first time a malformed frontmatter caused a build to fail with a clear error.

None of these refinements change the architecture. They are corrective actions on how the architecture is used.