Neonvil Logo
Budget Calculator
Neonvil Logo

contact@neonvil.com

© 2026 Neonvil Inc.


Budget Calculator
Back to Case Studies
FEATURED INSIGHT
One Firebase project — one Firestore, one functions deploy, one hosting bundle — yet it cleanly multi-tenants AI generation, metered credit billing, hashed API-key auth for arbitrary consumer sites, and a fire-and-forget GitHub Actions publish pipeline. No custom infra.

NEONVIL: Blog Copilot

A Case Study: A Multi-Tenant AI CMS on a Single Firebase Project
Blog Copilot Multi-Tenant AI CMS

Business Outcome

A production multi-tenant AI CMS delivered on a single Firebase project — authoring UI, orchestration backend, public SDK, and publishing pipeline — with AI generation, metered credits, and hashed API-key auth wired throughout.

Single-Project, Multi-Tenant

One Firebase project hosts the authoring UI, orchestration backend, CMS SDK, and publishing pipeline — with tenant isolation from the first read to the last write.

Metered AI at the Transaction Level

Every AI generation and SDK read is atomically charged against credits before the expensive call fires — no double-charging, no leaked free ops on failure.

Fire-and-Forget Publishing

A dispatch fans out to consumer sites with no webhook back — keeping the CMS decoupled from the consumer's CI and letting each site rebuild on its own cadence.

Context & Challenges

Technical writers and indie creators draft rough notes in scratchpads but lose hours each week turning them into publishable markdown, syndicating to LinkedIn, and wiring the finished posts into JAMstack sites. Blog Copilot collapses that loop into a single multi-tenant product — AI generation, credit-based billing, publishing, and a public SDK for consumer sites — all on one Firebase project, with no custom infrastructure.

Four critical engineering challenges:

01

Cloud Function timeout vs. long-running LLM calls: per-function timeout raised to 540s for the blog generator and 300s for the LinkedIn generator, sized to the Gemini 2 Flash p95 budget.

02

Atomic metered billing across heterogeneous ops: a single transactional helper wraps every metered action — AI generation, SDK reads, asset uploads — and insufficient credits raise 402 before the expensive call ever fires.

03

API-key auth for an arbitrary number of consumer sites: 32-byte keys shown once, SHA-256 hashes persisted, verified in a single collectionGroup lookup keyed on the hash — with lastUsedAt bumps and per-key revocation.

04

No official LinkedIn metrics API without paid tier: a 12-hour scheduled function fetches each published post's public URL, regex-parses reactions and comments, writes to the post document, and throttles inter-request to avoid IP blocks.

Project Goals

Backoffice SPA

A React 18 + Vite + MUI 5 authoring experience with MDX editing, an AI generation drawer, and credit-aware UI.

Cloud Functions Backend

23 Node 20 functions across 7 bounded contexts — posts, assets, github, linkedin, apiKeys, sdk, tenants — with tuned AI generation, atomic metered billing, and multi-tenant auth.

Public CMS SDK

A two-method TypeScript client that returns YAML-frontmatter MDX, dropping straight into a .mdx file on any JAMstack consumer site.

Our Solution

Three layers, one Firebase project. The backoffice is where users draft and generate. The Cloud Functions backend runs the AI flows, meters the credits, and enforces tenant isolation. The SDK and publishing pipeline deliver finished posts to arbitrary consumer sites.

AUTHORING
Backoffice SPA

MDX editor, AI drawer, and credit-aware UI

MDX-first post editor with split edit/preview tabs and auto-parsed tags

AI Drawer re-seeds from raw notes on every open — regenerate without losing context

Settings for API keys, GitHub integration, LinkedIn connection, and plan management

RTK Query with tag-based cache invalidation across 19 endpoints

ORCHESTRATION
Cloud Functions Backend

AI generation, metered billing, tenant-scoped auth

Posts
Assets
GitHub
LinkedIn
API Keys
SDK
Tenants

Two tuned AI flows: long-form blog (Gemini 2.0 Flash, temp 0.4, 2048 tokens) and LinkedIn promo (temp 0.7, 1024 tokens, 400-char hard cap in the prompt)

Atomic credit charging: a single transactional helper wraps every metered op, rejecting with 402 before the expensive call fires

API-key auth with SHA-256 hashing; keys verified via a single collectionGroup lookup with no tenant hint required

Per-function config: 540s for generation, 60s for CRUD, a 12-hour scheduled trigger for LinkedIn metrics

DELIVERY
📦
SDK & Publishing

How finished posts reach consumer sites

CMS SDK
GitHub Dispatch
LinkedIn
Signed URLs

Public TypeScript SDK returns YAML-frontmatter MDX that drops straight into a .mdx file

GitHub Actions dispatch with tenantId and trigger inputs — fan-out to rebuild consumer sites with no webhook back

LinkedIn posting with per-tenant OAuth tokens; engagement metrics scraped on a 12-hour cron

Direct-to-Storage signed PUT URLs for assets — browser uploads bypass function egress and timeouts

Effort Allocation

100%

AI Prompt & Generation Flows (20%)

Backend Business Logic (25%)

Backoffice UI/UX (25%)

Integrations (GitHub + LinkedIn) (15%)

Infrastructure & Deploy (10%)

Security & Auth (5%)

Infrastructure & Technologies

AI

Gemini 2.0 Flash via Vertex AI

Chosen for the lowest p95 latency in the Gemini 2 line — fits inside the 540s budget while staying cheap enough to run per-user on a free tier, with native Vertex auth that avoids API-key management entirely.

COMPUTE
🟧

Firebase Cloud Functions v2

Chosen for per-function timeout, memory, and concurrency config — 540s for the LLM flow, 60s for CRUD, a scheduled trigger for metrics sync, all in one deploy unit.

DATA
🔍

Firestore + collectionGroup

Chosen so API-key verification is O(1) across all tenants without knowing the tenant upfront — a collectionGroup query on the hash index makes auth a single read, no separate database required.

STATE
🔁

Redux Toolkit + RTK Query

Chosen to replace both the HTTP client and the cache layer for 19 endpoints with tag-based invalidation — the auth-token injection is a single prepareHeaders override rather than scattered interceptors.

PRAGMATISM
🟨

Plain JS on the backend

Chosen deliberately over TypeScript: no build step means faster cold deploys and smaller artifacts, and the CRUD-shaped surface area didn't justify the TS tax for a small team.

FRONTEND
🎨

MUI 5

Chosen to buy accessible primitives (Drawer, Autocomplete, table) for a back-office entering pilot — freeing engineering time for the AI flows and publishing pipeline, where differentiation actually lives.

DEVTOOLS

Turborepo + pnpm workspace

Chosen so backoffice, backend, and published SDK share types and scripts — Turbo cache keeps local typecheck under 5 seconds across the monorepo.

Back to Case Studies