Building html-editor.registermysite.com
Complete step-by-step guide to the Cloudflare Workers AI + Vectorize + R2 RAG system that generates websites from stored HTML templates.
1. Overview
This project extends Cloudflare’s official
llm-chat-app-template into a specialized HTML generator.
When a user asks for a page, the Worker:
- Embeds the user request with Workers AI
- Searches a Vectorize index for similar HTML templates
- Loads the full HTML of the best matches from R2
- Injects those templates into the system prompt
- Streams a complete HTML document from an LLM
The frontend shows a live CodeMirror editor and iframe preview while the model generates, and users can save results to a personal dashboard (browser localStorage).
2. Architecture
@cf/baai/bge-base-en-v1.5 (768 dimensions)
html-templates — semantic search over template embeddings + metadata
html-templates — full HTML files (avoids Vectorize metadata size limits)
@cf/meta/llama-3.1-8b-instruct-fp8 (32k context) streams the final page
public/ — chat UI, dashboard, docs, admin
3. Prerequisites
- Node.js 18+
- Cloudflare account with Workers AI, Vectorize, and R2 enabled
- Wrangler CLI (
npm i -g wrangleror use the project local binary) - Logged in:
npx wrangler login
4. Start from the official chat template
git clone https://github.com/cloudflare/templates.git
cd templates/llm-chat-app-template
npm install
Or use the project already structured under this repo
(src/, public/, wrangler.jsonc).
If you hit a peer dependency conflict between wrangler and
@cloudflare/workers-types, align workers-types to the
version wrangler expects (e.g. ^5.20260811.1), then:
rm -rf node_modules package-lock.json
npm install
5. Create Vectorize index and R2 bucket
Run these once per account (names must match the bindings):
npx wrangler vectorize create html-templates \
--dimensions=768 \
--metric=cosine
npx wrangler r2 bucket create html-templates
768 dimensions matches
@cf/baai/bge-base-en-v1.5. Cosine similarity is the usual
choice for semantic search over text embeddings.
6. Add bindings in wrangler.jsonc
{
"ai": { "binding": "AI" },
"vectorize": [{
"binding": "VECTORIZE",
"index_name": "html-templates"
}],
"r2_buckets": [{
"binding": "TEMPLATES_BUCKET",
"bucket_name": "html-templates"
}],
"assets": {
"binding": "ASSETS",
"directory": "./public"
}
}
Update src/types.ts so Env includes
AI, VECTORIZE, TEMPLATES_BUCKET,
and ASSETS.
7. Ingesting HTML templates
Templates are not loaded automatically from the
sample-templates/ folder. That folder is only a local
source of HTML. At runtime the Worker only sees what is in R2 + Vectorize.
Ingest API
POST /api/ingest body:
{
"id": "saas-landing",
"name": "Modern SaaS Landing Page",
"description": "Dark SaaS landing with hero, features, and pricing…",
"tags": "saas,landing,pricing,dark",
"html": "<!DOCTYPE html>..."
}
What the Worker does
- Stores full HTML in R2 at
templates/{id}.html - Builds embed text:
name + description + first 1,500 chars of HTML - Runs the embedding model
- Upserts vector + metadata (
name,description,r2Key,tags) into Vectorize
Example curl
HTML=$(cat sample-templates/saas-landing.html | jq -Rs .)
curl -X POST https://html-editor.registermysite.com/api/ingest \
-H "Content-Type: application/json" \
-d "{
\"id\": \"saas-landing\",
\"name\": \"Modern SaaS Landing Page\",
\"description\": \"Dark-themed SaaS landing with hero, features, pricing\",
\"tags\": \"saas,landing,pricing,dark\",
\"html\": $HTML
}"
Management
GET /api/templates— list objects in R2DELETE /api/templates?id=saas-landing— delete R2 object + Vectorize vector
/api/ingest and delete
routes in production (Cloudflare Access, secret header, or auth).
They ship open for local development convenience.
Recommended template size
| Size | Guidance |
|---|---|
| 5–20 KB | Best for quality + prompt fit |
| Up to ~50 KB | Still usable |
| Much larger | Split into sections; only first 1.5k chars are embedded |
8. RAG chat handler
On POST /api/chat the Worker:
- Reads the latest user message
- Embeds it with
bge-base-en-v1.5 - Queries Vectorize (
topK: 3, full metadata) - Fetches each match’s HTML from R2 via
r2Key - Builds a system prompt that includes the retrieved HTML (capped ~6,000 chars each)
- Calls the chat model with streaming and returns SSE
If retrieval fails, chat still works without context — RAG is additive, not required for the request to succeed.
System prompt goals
- Prefer adapting retrieved templates over inventing everything
- Output a complete self-contained HTML document
- CSS in a
<style>tag; JS only if needed
9. Frontend features
Implemented under public/:
- Streaming chat with live extraction of HTML from the model output
- CodeMirror editor (
htmlmixedmode, material-darker theme) - Live iframe preview updated while generating and while editing
- Left drawer with five example prompts
- Copy / Download / Save to Dashboard actions
- Responsive layout — side-by-side on desktop, tabs on mobile
- Login button stub for future OAuth
10. User dashboard (My Templates)
/dashboard.html stores user-generated pages in
browser localStorage (per device, no account required yet).
- From the generator: Save to Dashboard → name prompt → stored with timestamp
- Dashboard: list (name + date), CodeMirror + live preview, rename, edit, save, delete, download
- Open in Generator loads the HTML back into the main editor
Storage key: html_gen_saved_templates. Cap: 50 templates.
When OAuth is added later, the same shape can move to R2/D1 per user.
11. Optimize HTML for embeddings
Retrieval quality depends heavily on ingest text:
- Name + description — most important; describe page type, sections, style, use-case in natural language
- First ~1,500 characters of HTML are embedded — put distinctive structure/classes near the top
- Avoid huge minified CSS/JS or data-URIs at the top of the file
- Use meaningful class names and short section comments
- Keep templates focused (one page type per file); split large designs
12. Generation parameters
Model: @cf/meta/llama-3.1-8b-instruct-fp8 (32k context).
Recommended inputs for full-page HTML with RAG:
const inputs = {
messages,
max_tokens: 6144, // room left for RAG context in the 32k window
temperature: 0.15, // deterministic structure
top_p: 0.9,
top_k: 40,
repetition_penalty: 1.08,
stream: true,
};
Avoid setting max_tokens near half the context when the
system prompt already contains multiple full templates — input + output
must fit in 32k tokens total.
13. Admin page
sample-templates/admin-dashboard.html is a simple
client-side password gate (default Password123) with links
to the generator, template list API, and Cloudflare product dashboards.
14. Deploy and operate
npm run deploy
After deploy:
- Ingest sample templates via
/api/ingest - Open the site and try an example prompt from the left drawer
- Save a result to My Templates and reopen it from the dashboard
Point a custom domain (e.g. html-editor.registermysite.com)
at the Worker in the Cloudflare dashboard.
Useful routes
| Path | Purpose |
|---|---|
/ | Generator UI |
/dashboard.html | Saved templates |
/docs.html | This documentation |
/api/chat | Streaming RAG chat |
/api/ingest | Add template (R2 + Vectorize) |
/api/templates | List / delete templates |
15. Quick checklist
- Clone or scaffold the Workers AI chat template
- Create Vectorize index (768, cosine) and R2 bucket
- Add AI, Vectorize, R2, Assets bindings
- Implement ingest + RAG chat routes in
src/index.ts - Ingest optimized HTML templates via
POST /api/ingest - Ship frontend: stream → CodeMirror → live preview → save dashboard
- Tune generation params for 32k context + RAG prompt size
- Deploy, attach domain, protect admin/ingest routes
Built on Cloudflare Workers AI · Vectorize · R2 · the official LLM chat template.