Sitios editables
A content editor without a CMS: a JSON schema draws the form, and publishing rebuilds the static page and commits it to git. Three client sites run on it.

Two coaches and a food engineer needed one-page sites they could change themselves: copy, photos, buttons, colors, which sections show. A hosted CMS meant a monthly bill, a login to another product and a page that fetches its content on every visit. Handing them the HTML meant a broken layout the first time someone pasted a quote.
Three decisions that shaped it.
- 01
Git is the database
Why. contenido.json in the repo is the source of truth. Publishing sends it to a single Vercel function that writes the JSON, the rebuilt index.html and any new photos as blobs, builds one tree and one commit through the GitHub Git Data API, and moves the branch. One commit means one redeploy. Undo is a new commit with the previous commit's tree, and only runs if the last commit came from the editor, so it never reverts a code change.
Trade-off. Every publish waits for a deploy, about a minute. The editor sends the whole content file, so two open tabs overwrite each other. Photos live in git history, so the editor resizes them to 1600 px in the browser before upload.
- 02
Render at publish time, serve static HTML
Why. render(template, content) is plain text in, text out, so the same function runs in node construir.mjs locally and inside the publish function. Visitors download one HTML file from the CDN and never call the function. Clients write plain text with three marks (*gold italic*, **bold**, Enter); everything else is escaped, and an empty field removes the block around it, so clearing a button's text removes the button.
Trade-off. Nothing on the page is dynamic. Forms open WhatsApp or email with a prefilled message instead of posting anywhere, and the preview needs a round trip to the function.
- 03
Copy the engine per site, don't package it
Why. Each site has its own repo, Vercel project, password and fine-grained GitHub token scoped to that repo only. Alimentación Integral Hoy got the editor first, then Natalia, then Lole. Copying the files let each site grow what it needed: Alimentación has video and photo lists, Lole has one generic list type, section reordering and a palette that derives eleven colors from six.
Trade-off. The three copies drifted. Natalia's editor has no search and no section order, and the empty-field checker only exists in two repos. A fix travels by hand.




Sitios editables
A content editor without a CMS: a JSON schema draws the form, and publishing rebuilds the static page and commits it to git. Three client sites run on it.
- Role
- Architecture and full build
- Stack
- HTML, Vanilla JS, Node.js, Vercel Functions, GitHub Git Data API

Two coaches and a food engineer needed one-page sites they could change themselves: copy, photos, buttons, colors, which sections show. A hosted CMS meant a monthly bill, a login to another product and a page that fetches its content on every visit. Handing them the HTML meant a broken layout the first time someone pasted a quote.
Three decisions that shaped it.
Git is the database
Why. contenido.json in the repo is the source of truth. Publishing sends it to a single Vercel function that writes the JSON, the rebuilt index.html and any new photos as blobs, builds one tree and one commit through the GitHub Git Data API, and moves the branch. One commit means one redeploy. Undo is a new commit with the previous commit's tree, and only runs if the last commit came from the editor, so it never reverts a code change.
Trade-off. Every publish waits for a deploy, about a minute. The editor sends the whole content file, so two open tabs overwrite each other. Photos live in git history, so the editor resizes them to 1600 px in the browser before upload.
Render at publish time, serve static HTML
Why. render(template, content) is plain text in, text out, so the same function runs in node construir.mjs locally and inside the publish function. Visitors download one HTML file from the CDN and never call the function. Clients write plain text with three marks (*gold italic*, **bold**, Enter); everything else is escaped, and an empty field removes the block around it, so clearing a button's text removes the button.
Trade-off. Nothing on the page is dynamic. Forms open WhatsApp or email with a prefilled message instead of posting anywhere, and the preview needs a round trip to the function.
Copy the engine per site, don't package it
Why. Each site has its own repo, Vercel project, password and fine-grained GitHub token scoped to that repo only. Alimentación Integral Hoy got the editor first, then Natalia, then Lole. Copying the files let each site grow what it needed: Alimentación has video and photo lists, Lole has one generic list type, section reordering and a palette that derives eleven colors from six.
Trade-off. The three copies drifted. Natalia's editor has no search and no section order, and the empty-field checker only exists in two repos. A fix travels by hand.



