Last Updated: 2026-05-01 Status: Future / not in current roadmap. Tracked here so we can pick it up cleanly when timing is right. Service: new
apps/blog-hostCloudflare Worker, depends onapps/blog-service+@vlozi/bloglayouts Related:layouts-vision.md,blog-engine-vision.md,integration-friction-vision.md
The pitch: a customer with no website at all — or a non-React site (Webflow, Framer, Carrd, static HTML) — gets a real, indexable blog at their own domain in under five minutes. No code on their site. No CSS. They write, we host.
TL;DR
Layouts ship to React/Next.js customers first. The hosted-blog surface unlocks everyone else — non-technical creators, Webflow / Framer / Carrd users, pre-launch founders without a website, and agencies who want to set up a blog for a non-React client.
Architecture: a new Cloudflare Worker (apps/blog-host) at the edge renders the customer's chosen layout to HTML on every request, caches at the edge with tag-based invalidation on publish.
Two phases: (1) <tenant>.vlozi.app/blog subdomain for everyone, free; (2) custom-domain CNAME (blog.theirsite.com) on paid tiers via Cloudflare for SaaS.
This doc is not on the roadmap. It's the design we'll use when hosted blog gets prioritized — gated on:
- React-SDK layouts have validated (telemetry shows >X% of customers picking a layout in onboarding and shipping a first post)
- Real demand signal from the non-React audience (signups checking "I don't have a website yet" in onboarding > some threshold, or active Webflow/Framer customer requests)
1. Why this exists
1.1 The market layouts MVP doesn't reach
The layouts MVP (React SDK) covers maybe 50% of vlozi's intended audience: indie founders running Next.js, technical creators on Vite/Astro, agencies building React-based client work. Everyone else needs a different door:
| Persona | Today's options | What hosted blog gives them |
|---|---|---|
| Creator on Webflow / Framer / Carrd | Use Webflow CMS (different platform), or DIY embed iframes (bad SEO), or self-host on Ghost (different ecosystem) | CNAME blog.theirsite.com → vlozi-hosted blog with their picked layout. Existing site stays untouched. |
| Pre-launch founder, no website yet | Notion-as-blog (no SEO), Substack (locked-in subdomain), Medium (rented dirt) | <theirslug>.vlozi.app/blog works the day they sign up. Custom domain when they're ready. |
| Agency setting up a blog for a static-site client | Build a custom Next.js blog, host it separately, link from their Webflow/Wordpress site | Pick a layout in the dashboard, CNAME the client's blog subdomain. Done in an hour. |
| Non-technical writer who values their own domain | Substack (*.substack.com) — but the domain isn't really theirs |
A real blog at blog.theirdomain.com with vlozi-grade hosting and any layout they want. |
1.2 The Substack thesis, applied right
Substack's growth lever was "write before you have a website." Most creators have an idea, a Notion doc, and zero infrastructure when they decide they want to publish. Substack lets them ship a post that day. The cost: their content lives on Substack's domain forever and looks identical to every other Substack.
Vlozi can offer the same "write today, infrastructure later" lever but on the customer's own domain with a layout they pick. That's the gap nobody's filled.
1.3 Why this is a separate vision doc
Hosted blog is a substantial infra project distinct from layouts. It needs:
- A new Cloudflare Worker codebase (
apps/blog-host) - Cloudflare for SaaS subscription + integration
- Cert provisioning + verification flow
- Tag-based cache invalidation infrastructure
- Pricing-tier decisions
- A separate go-to-market story (different audience, different messaging)
Bundling it with layouts MVP would have:
- Doubled the launch timeline
- Forced premature decisions on hosted-blog pricing
- Spread layout-quality work thin while trying to land infra
Better to ship layouts on the React surface, validate the layout system itself, then bring that proven layout source to the hosted surface. The layout authoring is reused unchanged.
2. Two phases
Phase 1 — <tenant>.vlozi.app/blog subdomain (free)
Every workspace gets a hosted blog at:
<tenant-slug>.vlozi.app/blogUses the layout the customer picked in the dashboard. Same content, same admin, no integration needed. The day they sign up, they can write their first post and share a link.
This is the growth lever for non-technical creators and pre-launch founders.
Pricing: free tier includes the subdomain blog with reasonable abuse caps (e.g. 100k requests/month per tenant — well above what an indie blog needs, low enough to prevent DDoS-by-popularity). Paid tiers raise the cap.
Phase 2 — Custom domain via CNAME (paid)
When the customer is ready to use their own domain:
blog.theirsite.com CNAME cname.vlozi.appVlozi handles the cert (Cloudflare for SaaS), serves the blog under their domain, with their picked layout. No code on their site whatsoever.
Pattern is:
- Site at
example.com(Webflow, Framer, Carrd, anything) - Blog at
blog.example.com(vlozi-hosted) - Header link from the site to the blog (a simple
<a href>in their site builder)
Pricing: custom domain is a paid-tier feature. Free tier gets <tenant>.vlozi.app/blog only. Specific tier mapping is a pricing-team decision before this ships.
2.1 Cost reality (Cloudflare for SaaS, 2026 pricing)
$0.10 per custom hostname per month ($1.20/year per CNAMEd customer)- Per-MB egress on the custom domain at standard CDN rates
- 5–15 min cert provisioning latency on first verification (TXT-record-based)
For a free tier with 10k tenants who all CNAME, that's ~$1.2k/month in Cloudflare hostname fees alone. Not catastrophic but not zero. Custom domain stays paid-tier.
3. Architecture
┌─────────────────────┐
│ Dashboard │
│ Layout picker UI │
│ Custom-domain UI │
└──────────┬──────────┘
│ writes
▼
┌─────────────────────┐
│ blog-service │
│ /admin/blog/config │
│ /admin/domains/* │
└──────────┬──────────┘
│ reads
▼
┌─────────────────────┐
│ blog-host │ ◀── Cloudflare for SaaS
│ Cloudflare Worker │ (cert provisioning,
│ apps/blog-host/ │ custom hostname routing)
└──────────┬──────────┘
│
┌────────────┴────────────┐
▼ ▼
<tenant>.vlozi.app blog.example.com
/blog (CNAMEd customer
domain)
Both render the same layout source
used by the React SDK + embed widget.3.1 Request flow
- Request hits
<tenant>.vlozi.app/blog/...orblog.theirsite.com/... - Cloudflare Worker (
apps/blog-host) receives the request - Worker resolves tenant by Host header:
- For
*.vlozi.app: parse subdomain, look up tenant_id by slug - For custom domains: look up tenant_id by
tenant_blog_config.custom_domain
- For
- Worker reads
layout,layout_version,tokens_jsonfromtenant_blog_config - Worker fetches posts/categories/tags from
blog-service(same internal API the React SDK uses, served from regional DB) - Worker imports the layout module (statically bundled — see 3.4) and renders the appropriate surface (index/post/category/etc.) to HTML
- Worker returns full SEO-ready HTML with proper meta tags, OG, canonical, sitemap, RSS, etc., plus a small client hydration script for navigation prefetch and search interactivity
- Cloudflare edge caches the response with tags
tenant:{id}:posts
3.2 Cache strategy
| Cache layer | TTL | Invalidation |
|---|---|---|
| Cloudflare edge cache | 5 min default; up to 1 h for tag/category list pages | Tag-based purge on publish — every cached response is tagged tenant:{id}:posts; the publish webhook calls Cloudflare's purge_by_tag API to drop the whole tenant's cache atomically |
Browser cache (Cache-Control) |
public, max-age=30, stale-while-revalidate=300 |
Browser revalidates within 30 s — a customer refreshing after publish always sees the new post |
| OG image cache | 24 h | Same tag-based purge on publish |
| RSS feed cache | 5 min | Same tag-based purge |
| Sitemap cache | 1 h | Same tag-based purge (less critical — Google re-fetches on its own schedule) |
The Cloudflare API token for purge_by_tag lives in the worker's environment, scoped to the cache zone. blog-service calls the worker's /internal/invalidate endpoint (HMAC-authenticated) on publish; the worker forwards the tag purge to Cloudflare. Same pattern as the rest of the platform's invalidation flows.
3.3 SEO / canonical handling
Hosted blog wins SEO because it's server-rendered HTML at the edge. Custom-domain mode means the customer's own domain accumulates the SEO equity, not vlozi.app.
Duplicate-content handling: when a custom domain is verified, the <tenant>.vlozi.app/blog URL emits <link rel="canonical" href="https://blog.example.com/..."> pointing at the custom domain. We don't 301 immediately — that breaks customers who are mid-migration and still linking to the vlozi.app URL from elsewhere. After 30 days of verified custom-domain traffic, the dashboard offers an opt-in 301 redirect ("ready to fully retire the vlozi.app URL?").
This is the only way non-technical customers get a fast, indexable blog without writing code.
3.4 Layout module bundling
The worker statically bundles the same layout source the React SDK uses (packages/blog-sdk/src/react/layouts/{id}/). Each layout is a fixed-shape TypeScript export — see layouts-vision.md section 17 — so importing it into the worker bundle is straightforward.
The worker uses the React Server Components rendering path (react-dom/server) for HTML output and emits a tiny client hydration script that handles:
- Link prefetching (hover prefetch for adjacent posts)
- Client-side search (if the layout's
searchModeis"instant") - Mermaid + Carousel hydration (already handled by
<BlogContent>)
Total client JS budget: ≤ 30 KB gzipped — much lighter than the React SDK because the worker handles all data fetching and rendering.
4. Schema additions (when this ships)
The layouts-vision schema (tenant_blog_config) gains two columns:
ALTER TABLE tenant_blog_config
ADD COLUMN custom_domain text,
ADD COLUMN custom_domain_verified_at timestamp;
-- For custom-domain lookup at the edge worker
CREATE UNIQUE INDEX ON tenant_blog_config (custom_domain) WHERE custom_domain IS NOT NULL;Plus a new table for domain-verification state:
CREATE TABLE tenant_domain_verification (
tenant_id text NOT NULL,
domain text NOT NULL,
txt_token text NOT NULL, -- the value the customer must put in DNS
status text NOT NULL DEFAULT 'pending', -- "pending" | "verified" | "failed"
attempts int NOT NULL DEFAULT 0,
last_checked_at timestamp,
verified_at timestamp,
cf_hostname_id text, -- Cloudflare for SaaS hostname ID for cleanup
created_at timestamp NOT NULL DEFAULT now(),
PRIMARY KEY (tenant_id, domain)
);The verification flow is async — blog-service polls Cloudflare's API for cert-issuance status on a schedule and updates status. Dashboard shows the current state with a "re-check now" manual override.
5. Dashboard UX
5.1 Custom-domain setup flow
- Customer enters their domain in
Settings → Blog → Custom Domain. - Dashboard generates a unique TXT-record verification token, calls Cloudflare to register the custom hostname, shows the customer:
- The required DNS records (TXT for verification, CNAME for traffic)
- A copy-paste UI per record
- Common-registrar instructions (Namecheap, GoDaddy, Cloudflare, Google Domains, etc.)
- State: "Pending verification — checking every 60 s. We'll email you when it's ready."
- On verification success: dashboard shows "Live!" with a link to the blog at the custom domain.
- On verification timeout (e.g. 24 h with no DNS update): email + dashboard banner with troubleshooting.
5.2 Fail states to handle gracefully
- DNS records never propagate (typo) — show a "DNS check failed" panel with
digoutput - DNS records correct but cert provisioning still pending — show "DNS verified, issuing certificate..." (5–15 min)
- Customer changes domain provider mid-flow — detect that the TXT record is gone and re-prompt
- Customer adds a domain that's already CNAMEd to another tenant (rare but possible — collision detection)
5.3 The "first 5 minutes" experience for hosted blog
Onboarding flow:
- Sign up → workspace created.
- Pick a layout (from layouts MVP) + pick 1-2 brand tokens (
accent+fontBody). - Choose: (a) "I have a website with React" → install SDK; (b) "I have a website without React" → set up custom domain; (c) "I don't have a website yet" → use
<tenant>.vlozi.app/blog. - For (b) and (c): write your first post immediately, the blog is already live.
- Share the URL.
Customer hits step 5 in under 5 minutes. That's the bar.
6. Open questions
| Question | Discussion |
|---|---|
| Pricing tiers for custom domain | What plan unlocks 1 custom domain? 5? Unlimited? Settled before this ships. Likely free tier = 0, starter tier = 1, pro tier = 5, enterprise = unlimited. |
| Pricing for hosted-blog egress | Worker invocations + cache misses + egress add up. Free tier needs an abuse cap. 100k requests/month per tenant is one option; could be lower. |
| Branding on free-tier hosted blogs | Free-tier <tenant>.vlozi.app/blog pages show a small "Powered by Vlozi" footer. Removing it is a paid-tier feature. Custom-domain pages on paid tiers don't show the footer at all. |
| Multi-region rendering | Cloudflare Workers run at 300+ edge locations. Posts come from regional DBs (per blog-service multi-region setup). Worker fetches from the nearest region — needs the existing blog-service multi-region routing to be solid first. |
| Wildcard vs per-tenant subdomains | All free-tier blogs live under *.vlozi.app. Cloudflare handles wildcard certs for the apex. Subdomain provisioning is just a DB write — no DNS changes needed per tenant. |
| What about subdirectory hosting? | "Can you host my blog at example.com/blog instead of blog.example.com?" → No. Subdirectory hosting requires their server (e.g. their Vercel / Netlify) to proxy to vlozi, which puts vlozi back in the integration-friction problem. CNAME-only. |
| Migration from existing platforms | When this ships, customers already on Substack / Ghost / Medium will want to import. Each is a separate ~1-2 day import path (see hosted-blog-import-vision.md — to be written when this gets prioritized). |
7. Build estimate (when this ships)
| Phase | Scope | Estimate |
|---|---|---|
| H1 — Worker scaffold | apps/blog-host Cloudflare Worker boilerplate, tenant-by-Host resolution, basic post fetch from blog-service |
2 days |
| H2 — Layout rendering at the edge | Bundle the layout module, render the appropriate surface to HTML, emit OG/RSS/sitemap routes | 3 days |
| H3 — Cache + invalidation | Edge cache with tag-based purge, blog-service /internal/invalidate HMAC endpoint, on-publish hook |
2 days |
| H4 — Custom-domain CNAME flow | Cloudflare for SaaS integration, TXT-record verification, cert provisioning, dashboard verification UI, error-state recovery | 3 days |
| H5 — Pricing-tier gating | Plan-based limits on hosted-blog access, custom-domain count, request quotas | 1 day |
| H6 — Subdomain provisioning | <tenant-slug> resolution, slug uniqueness, slug-change handling (e.g. customer renames workspace) |
1 day |
| H7 — Docs + onboarding flow | Onboarding flow update with "I don't have a website" branch, integration-prompt updates, hosted-blog quickstart | 1 day |
| Total | ~13 working days (~2.5 weeks) |
This is its own ~2.5-week project — gated on layouts being live and proven. Don't start before MVP layouts have real customers.
8. What this unblocks
When hosted blog ships:
- Webflow / Framer / Carrd customers can use vlozi at all. Currently they can't.
- The "free blog at signup" Substack-style growth lever. Pre-launch founders ship a blog the day they discover vlozi.
- Agencies can serve non-React clients without ejecting from vlozi. A bigger total addressable market for the platform.
- The marketing site can show a "no code? no website? no problem" track. Currently the integration-quickstart implicitly assumes a Next.js codebase.
- A realistic compete with Substack / Beehiiv / Ghost. Vlozi gains the "your domain, our infrastructure, any layout" pitch that none of those three offer in combination.
9. What this doesn't change
The React SDK keeps working unchanged. Customers who installed @vlozi/blog in their Next.js app keep using it. Hosted blog is purely additive — a second front door, not a replacement for the first. The same blog content is reachable from both surfaces with consistent admin experience.
10. Summary
Don't build this yet. Build layouts on the React surface first. Validate that customers actually pick a layout, theme it, and ship a first post. When that's working — and when telemetry shows real demand from non-React audiences — pick this doc up and execute the ~2.5-week plan in section 7.
Until then, this exists so we can answer "what about Webflow customers?" with "we have a plan, it's just not next."