Build Siter.io
KINDAback to the verdict
A structured site builder whose hard problems are publishing a whole site atomically with a way back, connecting a bare apex domain that the DNS standard will not let you point with a CNAME, and never letting editable content become executable markup.
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 static build.
Get it nodejs.org, or nvm install 22. open ↗
Verify
node --version prints v22 or higher - have readyfree to a few pounds a month
Why The output is plain files, and the whole publish design assumes a host that serves a directory and lets you swap which directory is live.
Get it Object storage with a static website mode, a small VPS with a web server, or any static host.
Verify
A directory of files you upload is reachable over HTTPS - have readyaround ten pounds a year
Why The apex versus subdomain problem is the phase that most needs a real zone, and a test domain with a provider you can log into is the only way to exercise it.
Get it Any registrar, ideally one whose DNS interface you can reach quickly while testing.
Verify
You can add and remove records and see them resolve - decidefree to a few pounds a month
Why This app deliberately is not a form backend, so you need to decide the destination before writing the contact section rather than inventing a database table for it later.
Get it Choose a mail address, a form service or an endpoint you already run, and write it down.
Verify
A test submission arrives at the chosen destination
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- sites(id, name, primary_host, www_redirect, created_at, published_version_id) - pages(site_id, id, path, title, description, noindex, ordinal) · path is the route, and uniqueness is enforced - sections(page_id, id, kind, ordinal, data_json) · kind names a renderer, data_json is typed per kind, and neither holds markup - assets(site_id, id, original_name, sha256, ext, width, height, bytes) · served as content-hashed filenames - versions(site_id, id, built_at, output_dir, file_count, bytes, built_from_revision) · every publish, kept for rollback - domains(site_id, host, kind, verification_token, dns_checked_at, dns_state, notes) · kind is apex or subdomain, which changes the records required - The section data is the document and the HTML is derived, so changing a renderer restyles every page that uses it without touching content.
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, versions and domains. |
OUTPUT_ROOT | required | ./data/published | One directory per version. The live pointer sits here too. |
ASSET_DIR | required | ./data/assets | Uploaded media, referenced by content hash. |
KEEP_VERSIONS | required | 10 | How many published versions to retain for rollback. |
EDITOR_BIND | required | 127.0.0.1:3000 | The editor is private. Only the output is public. |
FORM_ENDPOINT | optional | https://forms.example.com/f/abc123 | Where contact sections post. Unset means the contact section shows an address instead. |
The build, in order
Sections as data, renderers as code
A page is a list of typed section records, and no user input is ever stored as markup.
The schema is what lets the renderer change independently.
Files
src/sections/schema.tssrc/sections/hero.tsxAn allow list of marks is a format; arbitrary HTML is a liability.
Files
src/richtext.tsThe cheapest guard against the shortcut creeping back in.
Files
tests/no-stored-markup.test.ts
done when · tick each as it passeswatch out- Storing each section as a blob of HTML feels like a shortcut and costs you both halves of the product at once: you cannot change a heading style across the site without a find and replace over content, and a paste from a document carries markup straight into the published page. Store fields, render through components, and never persist markup.
A preview that is actually that width
The responsive preview reports the viewport width it claims, so breakpoints fire where they will in the browser.
The viewport width is the only thing breakpoints listen to.
Files
src/editor/Preview.tsxShow the number so a wrong preview is visible, not suspected.
Files
src/editor/Preview.tsxOne manual check that the simulation is honest.
Files
docs/preview-verification.md
done when · tick each as it passeswatch out- Simulating a narrow screen by squashing the preview element leaves the document's viewport width unchanged, so the mobile breakpoint never fires and the page looks perfect in the editor and wrong on a phone. Set the iframe's actual width to the target, then scale the whole iframe with a transform for display.
Publish all of it or none of it
A publish is a new immutable version and the live site flips between versions in one step.
Immutable output is what makes rollback trivial.
Files
src/publish/build.tsA broken link found in a directory beats one found by a visitor.
Files
src/publish/verify.tstests/link-crawl.test.tsThe whole atomicity story in one operation.
Files
src/publish/activate.ts
done when · tick each as it passeswatch out- Publishing by overwriting the live directory file by file leaves visitors mid-publish seeing new pages referencing assets not yet uploaded, and a failure halfway leaves the site permanently in that state with no record of what changed. Build into a new directory, verify it, flip the pointer, and keep the old one so rollback is a flip rather than a rebuild.
The apex domain problem, explained properly
Connecting a domain succeeds or explains exactly which record to add, including the case the DNS standard forbids.
They are different problems with different records.
Files
src/domains/instructions.tsDNS advice without a lookup is astrology.
Files
src/domains/verify.tsThe simplest correct answer for most people.
Files
src/publish/redirects.ts
done when · tick each as it passeswatch out- Telling every user to add a CNAME works for www and fails silently for the bare domain, and because some providers accept the record and then break mail or the whole zone, the user's first symptom is unrelated and awful. Detect apex versus subdomain, give the right instruction for each, and refuse to claim the domain is connected until you have resolved the expected records yourself.
Leaving with everythingproduct builder
The site, its content and its history can be taken elsewhere without this application.
One of the two alone is a lock-in with good manners.
Files
src/export/site.tssrc/export/content.tsThe only proof that the export is complete.
Files
tests/export-roundtrip.test.tsA form backend is a spam problem and an inbox, not a feature.
Files
src/sections/contact.tsxREADME.md
done when · tick each as it passeswatch out- A builder whose export is a rendered snapshot but not the content behind it leaves the user able to host their site once and never edit it again, which is exactly the trap a paid builder is accused of. Export both the built files and the structured content, and document the content format well enough that a different renderer could consume it.
That is the whole plan for Siter.io. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- A freeform visual editor.
- Figma import.
- A template marketplace.
- Certificate issuance and edge hosting.
- general-purpose visual editing
- large template and component libraries
- CMS collaboration and client handoff
- managed hosting, forms, and analytics
- A second theme applied to the same content, which the section model already makes possible
- Scheduled publishing, which is a timer on an operation that is already atomic
- A read-only share link for a version, useful for client review before the pointer flips
Need the files? The project pack on the verdict page hands your agent the whole brief · more website builders.