Build Swipe Pages
KINDAback to the verdict
A section-based page builder whose hard problems are publishing without ever serving a half-written page, keeping the editor's own code out of what visitors download, and refusing to publish a page that misses a stated performance budget.
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 publish pipeline.
Get it nodejs.org, or your version manager. open ↗
Verify
node --version prints v22 or higher - installfree
Why The budget gate needs a real measurement rather than an estimate, and this is the part people skip and then wonder why the page is slow.
Get it Install a headless browser your measurement script can drive.
Verify
The measurement script prints paint and interaction figures for a sample page - accountfree to a few pounds a month
Why The published output is plain files, so this can be a small server, object storage or a static host, and picking it first stops the output drifting towards one vendor's conventions.
Get it Any host that serves a directory over HTTPS.
Verify
An exported folder serves correctly from it with no build step - have readyaround ten pounds a year
Why Custom domain verification and certificate issuance are the two steps that turn this from a demo into something you can point traffic at.
Get it Any registrar; you need to be able to add a TXT and an A or CNAME record.
Verify
A TXT record you add is visible from a public resolver - decidefree
Why Chosen before the first section is written, these constrain the design; chosen afterwards they get raised until they pass.
Get it Write down the HTML, CSS and JavaScript byte ceilings, the third-party origin count, and the paint and interaction figures you will hold to.
Verify
The numbers appear in the configuration file and in the README
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- pages(id, slug, title, created_at, archived_at) - page_versions(id, page_id, version, sections_json, theme_json, created_at, published_at) · immutable once published - sections(version_id, ordinal, kind, props_json) · kind is one of a closed list, and an unknown kind fails validation rather than rendering empty - assets(id, page_id, path, sha256, width, height, bytes, mime) · dimensions are stored because the markup needs them - publishes(id, version_id, output_dir, started_at, finished_at, state, bytes_html, bytes_css, bytes_js, lcp_ms, inp_ms, rolled_back_from) - submissions(id, page_id, version_id, received_at, fields_json, source_ref, spam_reason) · written before anything else runs - domains(id, page_id, hostname, verified_at, cert_state) - Every published page maps to exactly one immutable version row, so rollback is pointing the symlink at the previous output directory rather than regenerating anything.
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/pages.db | Pages, versions, assets, publishes and submissions. |
OUTPUT_ROOT | required | ./out | Content-addressed publish directories and the served symlink. |
PUBLISH_KEEP | required | 10 | How many previous publishes to retain for rollback. |
BUDGET_JS_BYTES | required | 0 | Zero is a reasonable starting value for a static landing page. |
BUDGET_CSS_BYTES | required | 30000 | Measured on the rendered output, not on source files. |
BUDGET_LCP_MS | required | 2500 | Check the current published threshold before setting this. |
BUDGET_INP_MS | required | 200 | Check the current published threshold before setting this. |
SUBMISSION_RATE_PER_HOUR | required | 20 | Per hashed address. Rejections are stored with a reason, not dropped. |
The build, in order
A page that is data
Sections are typed rows validated against a closed list, and the HTML is derived from them.
Every open field is an unbudgeted script waiting to happen.
Files
src/sections/schema.tssrc/sections/index.tsDeterministic output is what makes rollback and hashing meaningful.
Files
src/render/page.tstests/render.test.tsIncluding in a field that looks harmless, like a footer note.
Files
src/sections/schema.tstests/no-raw-html.test.ts
done when · tick each as it passeswatch out- An open-ended section that accepts arbitrary HTML turns the builder into a text editor with extra steps, and the first time a client pastes an embed the page ships a third-party script you never budgeted for and cannot measure. Keep the list closed, validate props against a schema, and make adding a kind a code change.
Publishing that cannot half happen
A publish writes a complete output directory and becomes live in one atomic step, with rollback.
The hash of the output is the name, so identical input publishes nothing new.
Files
src/publish/render.tsOne syscall, not a copy loop through a live directory.
Files
src/publish/swap.tstests/publish-atomic.test.tsterminalln -sfn out/<hash> out/current.tmp && mv -Tf out/current.tmp out/current
The cheapest possible fix at eleven at night.
Files
src/publish/rollback.ts
done when · tick each as it passeswatch out- Rendering straight into the served directory means a visitor arriving during the write gets a page with half its stylesheet, and because the write takes under a second you will never reproduce it and will blame the host. Render elsewhere, verify the directory is complete, then swap.
Responsive without a breakpoint editor
One authored layout adapts by rule, and images never move the page while it loads.
Two numbers per scale beats four breakpoints per element.
Files
src/render/theme.tsEmitted into the markup so nothing shifts as images arrive.
Files
src/assets/upload.tstests/assets.test.tsNobody checks the phone view if checking it is a separate click.
Files
src/app/editor/preview.tsx
done when · tick each as it passeswatch out- Letting the editor set values per breakpoint feels like control and produces four layouts that drift apart, so the phone version is six months behind the desktop one and nobody notices because nobody previews it. Author once with rules, and let the preview show three widths side by side rather than offering three editors.
A performance budget that blocks the publish
A page that misses the stated budget cannot go live until it is fixed or the budget is changed on purpose.
Before it is live, not in a report afterwards.
Files
src/publish/budget.tstests/budget.test.tsCheck the current published thresholds for the metrics you record.
Files
scripts/measure.mjssrc/publish/measure.tsA warning in a build log is not a budget.
Files
src/publish/index.ts
done when · tick each as it passeswatch out- Shipping the editor's own framework to the visitor is the standard way a page builder produces slow pages, because the bundle that makes the editing experience pleasant is downloaded by every visitor who will never edit anything. Publish static files with a JavaScript ceiling measured in kilobytes and let the gate fail the publish when a section kind starts importing a component library.
Forms, a domain, and the folder you can take awayproduct builder
Submissions land before anything else runs, the domain is yours, and the whole site exports as files.
The lead is the product; a notification is a convenience.
Files
src/app/api/submit/route.tstests/submit.test.tsA silent spam filter eating real submissions is undetectable.
Files
src/forms/filter.tsThe check that you are not trapped in your own builder.
Files
src/export/page.tstests/roundtrip.test.ts
done when · tick each as it passeswatch out- Sending the submission to an email address or a webhook from inside the request handler means a slow receiver holds the visitor's browser open and a timeout loses the lead entirely, which on a landing page is the only thing of value the page produced. Write the row first, respond, and let anything else happen afterwards and be allowed to fail.
That is the whole plan for Swipe Pages. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Arbitrary visual positioning and a template library.
- Split testing with a significance verdict.
- Managed hosting with traffic allowances.
- A visitor analytics script.
- large template and component libraries
- CMS collaboration and client handoff
- managed hosting, forms, and analytics
- general-purpose visual editing
- Field-measured metrics collected from real visits rather than a headless run, once a page has traffic
- A second symlink for a staging hostname pointing at the next publish, so a page can be reviewed live before the swap
- A per-section budget attribution, so the gate can say which section added the bytes
Need the files? The project pack on the verdict page hands your agent the whole brief · more website builders.