Build Mixo
A landing page builder whose hard problems are keeping a page as data so regeneration cannot eat hand edits, publishing static files within a host's real limits with a rollback, and collecting email addresses from a static site without pretending to be a mailing list provider.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Runs the editor and the build.
Get it nodejs.org, or nvm install 22. open ↗
Verify
node --version prints v22 or higher - API keypennies per page, and it is worth setting a hard cap
Why Generation fills section fields. One provider behind one interface is enough, and the app must stay usable with the key absent.
Get it Create a key with your chosen provider and set a spending limit on it.
Verify
A section generates and validates against its schema, and removing the key leaves the editor working - accountfree tiers are common for a personal site
Why Publishing is the phase where guessed limits cause silent failures, so the numbers come from the host's own documentation.
Get it Choose a static host, read its current limits page, and record the figures with the date. open ↗
Verify
The README carries the limits and the date, and the publish step checks against them - have readyroughly ten to fifteen pounds a year
Why A landing page on a host's subdomain cannot be moved without losing every link to it, and moving is the point of owning the output.
Get it Register a domain and point it at the static host per its documentation.
Verify
The published site answers on your domain over HTTPS - decidefree
Why The generator checks numbers against this list, so writing it first is what makes the check meaningful rather than decorative.
Get it Write down every figure and proof point that is true about the thing you are launching, with where each came from.
Verify
Each appears in the facts table with its evidence
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- sites(id, name, domain, created_at, published_version) - pages(id, site_id, path, title, meta_description, created_at, updated_at) - sections(id, page_id, ordinal, kind, fields_json, locked, source) · source is generated, edited or imported - facts(id, site_id, key, value, evidence) · every number that may appear on the page, with where it came from - versions(id, site_id, built_at, manifest_json, bytes_total, file_count, published_at, rolled_back_at) - signups(id, site_id, email, created_at, consent_text, source_page) · plain storage, with a documented export - A section is typed fields, never a blob of markup, which is what makes regenerating one section safe for the others.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
DATABASE_PATH | required | ./data/sites.db | Sites, pages, sections, facts and versions. |
MODEL_API_KEYsecret | optional | sk-...redacted | Generation only. Without it the editor still builds and publishes. |
MODEL_NAME | optional | a current small text model | Recorded on every generated section. |
BUILD_DIR | required | ./out | Static output. What gets published, and nothing else. |
PAGE_WEIGHT_BUDGET_KB | required | 500 | A page over this fails the build. |
SIGNUP_ENDPOINT_URL | optional | https://forms.example.com/signup | Where the published form posts. Empty means the form is not rendered. |
The build, in order
A page that is data before it is HTML
Sections are typed fields and one deterministic renderer turns them into a page.
Six kinds that cover a landing page beats forty that cover everything.
Files
src/lib/sections.tssrc/lib/sections.test.tsTwo ways to produce HTML means two ways to produce different HTML.
Files
src/render/page.tsA build you cannot reproduce is a build you cannot roll back to.
Files
tests/deterministic-render.test.ts
done when · tick each as it passeswatch out- Storing each section as a blob of HTML because the model produced HTML means every later edit is a string operation, two sections can define the same class name with different meanings, and nothing can be restyled without a find and replace across every page you ever generated. Type the fields, keep markup out of the database, and let the renderer own every tag.
Generation that fills fields and invents no facts
The model writes into named fields, and any number it produces must match a recorded fact.
Structured fields in, structured fields out, or nothing.
Files
src/lib/generate.tsThe one check that keeps a demo site from lying about a real business.
Files
src/lib/claims.tstests/claims.test.tsNobody should lose a rewritten headline to a regeneration.
Files
src/lib/regenerate.ts
done when · tick each as it passeswatch out- Asking a model for a landing page and storing whatever comes back gets you 'trusted by over 2,000 teams' on the site of a product with four customers, and because it reads exactly like the rest of the copy nobody deletes it before the page goes live. Gate every numeric claim on a recorded fact, and refuse the section rather than publishing an invented one.
A build that a browser can load on a bad connection
The output is static files, sized and linked correctly, with no runtime requirement.
A hero image is not a photograph archive.
Files
src/build/images.tsPublishing is the wrong time to find out.
Files
src/build/links.tsIt is what makes the next phase's limits checkable.
Files
src/build/manifest.ts
done when · tick each as it passeswatch out- Shipping the original photographs straight from the upload puts four megabytes of hero image on a page whose entire text is two hundred words, and the person on a train sees nothing for eight seconds. Resize at build time to the widths the layout uses, record the total in the manifest, and fail the build over a stated page weight budget.
Publishing inside the host's real limits, with a way back
A deploy is a tagged version that can be rolled back, and the limits are checked before the push.
Read them from the host's own documentation, and record when you read them.
Files
src/publish/limits.tsRollback should not depend on your build still being reproducible today.
Files
src/publish/versions.tsSoft limits fail quietly, which is the worst way to fail.
Files
src/app/publish/page.tsx
done when · tick each as it passeswatch out- Treating publish as a file copy means the first bad deploy is live until you rebuild, and a host's soft build limit turns an afternoon of small fixes into a site that silently stops updating while you keep pressing publish. Tag every version, check the manifest against the host's stated limits first, and make rollback a single command that does not rebuild anything.
Email capture that is honest about where the address goesproduct builder
A static page can collect an address, and the person is told what happens to it.
A list without it is a list you cannot use.
Files
src/app/api/signup/route.tsAbuse control should not become tracking.
Files
src/lib/ratelimit.tsThe export is the only thing that outlives this app.
Files
src/lib/export.ts
done when · tick each as it passeswatch out- Collecting addresses with no record of what the person agreed to leaves you, a year later, with a list you cannot lawfully or honestly send to, because nobody can say what was on the page when each address was typed. Store the consent text verbatim with every signup, and version it when the wording changes.
That is the whole plan for Mixo. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Sending email to collected addresses.
- A free-form visual editor.
- A template marketplace and asset library.
- Hosted analytics and visitor tracking.
- sender reputation, deliverability, and suppression handling
- general-purpose visual editing
- large template and component libraries
- CMS collaboration and client handoff
- managed hosting, forms, and analytics
- A second page type such as a changelog, which is the same sections against a different layout
- Multilingual pages, easy while sections are data and painful once they are markup
- A scheduled rebuild that refreshes only pages whose facts have changed
Need the files? The project pack on the verdict page hands your agent the whole brief · more website builders.