Provides an Astro + Cloudflare starter template with a skeleton including a media pipeline, design tokens, and form endpoints.
website-build-kit — Astro Business Template
By nurkamol
website-build-kit is a method and starter template for building and delivering production marketing sites, including an Astro + Cloudflare stack, skill plugins, and inspection tools, with an emphasis on verifying actual results after deployment.
Explore the design
Compare the page layouts and mobile experience. Open any screenshot for a closer look.
Actual screenshot captured by the server browser.
All pages & views 2
Screenshots show visual appearance; interactive behavior may differ.
What stands out
A quick read of the recorded project description.
Skill plugins are triggered by description matching and support commands such as /website-build.
Includes multiple verification scripts, such as npm run verify and npm run a11y, that check the deployed site.
The default stack includes Astro static generation, Cloudflare Workers, PagesCMS, Brevo, and more.
The template does not preset visual design, avoiding all sites looking identical.
Template Details
website-build-kit provides a marketing site build method from discovery to delivery, plus an Astro + Cloudflare starter template. The README states its value lies in accumulated decisions and a silent-failure checklist, not in a theme. The template does not preset a color palette, fonts, or homepage, but includes a media pipeline, design tokens, form endpoints, SEO, and multiple verification scripts. Skills are triggered by description matching, so there is no need to memorize commands. The README also provides installation and usage instructions, along with detailed reference documentation.
Is it right for your project?
Use Cases
- Migrating an existing website (for example, from Elementor) to Astro and Cloudflare.
- Building a marketing website from scratch for a business without an existing site (for example, a dental clinic).
- Repairing and maintaining a delivered website that has drifted.
- Running automated checks in CI to ensure the deployed website meets expectations.
Suitable For
- Development teams that need to build production marketing websites.
- Developers who want a structured approach to avoid silent failures.
- Teams using the Astro and Cloudflare stack.
- Website projects that need accessibility and SEO checks.
Getting started & customization
Use these recorded setup notes alongside the repository instructions for your chosen version.
- 1
Install the plugin via /plugin marketplace add nurkamol/website-build-kit and /plugin install website-build@website-build-kit.
- 2
Or clone the repository and run ./install.sh to symlink skills and commands into ~/.claude.
- 3
Create a new project with npm create website-build-kit.
- 4
Describe requirements directly in the project to trigger skills, or use the /website-build <url> command.
License & source material
Usage conditions & limitations
Free to use · keep license noticeThis entry covers the free version under the MIT license in the public repository; the free scope is the code of that version and does not mean external services or third-party assets are free.
Must comply with when using
- Permits free use, modification, commercial use, and distribution of that version's code.
- When distributing copies or substantial portions of the code, retain the original author's copyright notice and the complete MIT license notice.
- The code is provided as is; warranties and limitations of liability are governed by the original license and applicable law.
Additional costs & items to verify
- Demo images, fonts, icons, and other third-party assets must be checked against their respective licenses and cannot be judged solely by the code's MIT license.
- External services such as domains, hosting, databases, payments, or AI may incur separate charges.
- The template requires a Node 22.12+ environment.
- The default stack depends on external services such as Cloudflare Workers, PagesCMS, Brevo, KV, GA4 or Cloudflare Web Analytics, and Turnstile.
- Features such as form email require configuring secrets (secrets declared in .dev.vars.example); when missing, the form still returns success but does not send email.
- npm run seo is an optional feature and depends on the external npm package @nurkamol/seo-audit.
- The site repair process documentation is located in a separate repository, site-runbooks, rather than in this repository.
- Client names and project details in the repository are shared confidentially; contact information, credentials, and analytics IDs are not allowed to appear in the repository.
View Original Text Basis
Permission is hereby granted, free of charge
Original License Text
Node 22.12+. It builds green with no content, no images and no secrets. Fill in
Original README
Astro static → Cloudflare Workers → worker assets (R2 past ~15 MB)
Original README
- **`npm run check:secrets`** — compares the secrets your code declares in `.dev.vars.example`
Original README
- **`npm run seo`** *(optional)* — [full-site SEO audit](https://www.npmjs.com/package/@nurkamol/seo-audit),
Original README
lives in [site-runbooks](https://github.com/nurkamol/site-runbooks), which calls this rather than
Original README
https://github.com/nurkamol/website-build-kit/blob/master/LICENSEhttps://github.com/nurkamol/website-build-kit/blob/master/README.mdCloudflare Workers. Client names and project detail in this repository are shared in
Original README
Compiled by DeepSeek based on repository license and README ·2026-10-04. Results only cover the materials read; dependencies, images, fonts, and subsequent term changes are subject to their respective original texts.
Project checks & description sources
Template Information
- Source platforms
- GitHub
- Documentation and maintenance reference score
- More documented · 92 / 100
- Technologies
- Astro · WordPress
- Website type
- Corporate Website · Starter
How this score is calculated
Our heuristic uses license records (up to 30), a demo link (12), a preview (10), stars (15), recorded maintenance (15), description completeness (8), feature records (5) and classification data (5). It is not a code, security or performance test, or a Google score. A link does not establish tested functionality.
What to verify for this project
- Repository update recorded
- 2026-09-30
- Screenshot last checked
- 2026-10-04
A demo link is listed. Check it with your own content and viewport sizes; its presence does not confirm that forms, authentication or payments work.
The catalog identifies Astro as the primary stack. Check the actual dependency manifest and lockfile for versions and hosting requirements. These notes use recorded metadata, not a code audit or a deployment test.
Use a selection guide →How this description was prepared
AI-assisted description based on catalog records and repository material. Repository authors are not automatically the reviewers of this description.
Review declaration: codex-user-requested-batch-review-20261017 · 2026-10-16
Inspect the recorded source material
SHA-256: eb7d0378efaace196738a9787a6cf405b4761bee6defb1380dc7074b6de55ed5
2026-10-04T20:30:28.447Z
Source file / repository<p align="center">
<!-- GitHub honours prefers-color-scheme in a <picture>. Without it the mark
is drawn in near-black and disappears for anyone reading in dark mode. -->
<picture>
<source media="(prefers-color-scheme: dark)" srcset="site/brand/mark-dark.png">
<img src="site/brand/mark-light.png" width="72" height="72" alt="">
</picture>
</p>
<h1 align="center">website-build-kit</h1>
<p align="center">
<em>Your build was green. The site was wrong.</em><br>
<a href="https://nurkamol.github.io/website-build-kit/">nurkamol.github.io/website-build-kit</a>
</p>
<p align="center">
<a href="https://www.npmjs.com/package/create-website-build-kit"><img src="https://img.shields.io/npm/v/create-website-build-kit?label=create-website-build-kit" alt="npm version"></a>
<a href="https://github.com/nurkamol/website-build-kit/actions/workflows/kit.yml"><img src="https://github.com/nurkamol/website-build-kit/actions/workflows/kit.yml/badge.svg" alt="CI status"></a>
<a href="LICENSE"><img src="https://img.shields.io/badge/licence-MIT-blue" alt="MIT licence"></a>
</p>
A method and a starter for shipping production marketing sites: discovery → stack and
provider decisions → build → verify against the deployed thing → hand over.
Not a theme. The value is the accumulated set of decisions and the list of things that fail
**silently** — clean build, clean types, clean deploy, wrong result.
```bash
/plugin marketplace add nurkamol/website-build-kit
/plugin install website-build@website-build-kit
```
Or clone and symlink, if you would rather not add a marketplace:
```bash
git clone https://github.com/nurkamol/website-build-kit.git
cd website-build-kit && ./install.sh
```
Then, in any project, **just ask**:
```
Rebuild https://acmeplumbing.com on Astro and Cloudflare.
We're migrating off Elementor — here's the site: https://example.com
Build a marketing site for a dental practice. Nothing exists yet.
```
The skill matches on description, so there is no command to remember. `/website-build <url>`
works too if you want to be explicit.
**→ [docs/walkthrough.md](docs/walkthrough.md)** — a worked example as a session, start to launch
**→ [docs/how-to-start.md](docs/how-to-start.md)** — the three entry points, what to have
ready before you begin, and what the first ten minutes should look like.
## What's in it
| | |
| --- | --- |
| `skills/website-build/` | The build skill. `SKILL.md` plus eight references |
| `skills/site-repair/` | The repair skill — for a site already delivered, and drifting |
| `commands/` | Slash-command entry points: `/website-build`, `/media-audit` |
| `template/` | Astro + Cloudflare starter that builds from a clean clone |
| `docs/` | How to use it, and how to extend it |
| `install.sh` | Symlinks skills and commands into `~/.claude` |
| `.claude-plugin/` | Plugin and marketplace manifests, so `/plugin install` works |
| `site/` | The landing page. Hand-written HTML — see the note in `pages.yml` |
| `create/` | The `npm create website-build-kit` scaffolder. Packs `template/` at publish time |
| `CLAUDE.md` | Applies when working **on the kit** — house style, where things go |
| `template/CLAUDE.md` | Ships with each new site — that site's conventions |
## The references
The skill loads these as needed rather than all at once.
| Reference | Contains |
| --- | --- |
| `kickoff.md` | Source import, three discovery rounds, feature catalogue, design token spec, mobile decisions |
| `stacks.md` | Migration playbook per source builder; the integration inventory; every provider decision and its default |
| `archetypes.md` | Page shape per site type — section order, proof model, where conversion sits, and the failure mode |
| `features.md` | The features that need a decision rather than a checkbox — 404, search, light/dark/auto, i18n, shortcuts |
| `design.md` | Full redesign — reference-gathering, comping two directions on staging, and the tells of a templated site |
| `build.md` | Standing instructions, phases with gates, definition of done |
| `compliance.md` | Which accessibility law binds this client — WCAG, ADA, EAA, Section 508, AODA — and what to build, test and publish |
| `traps.md` | Silent failures, with the symptom and the fix |
`traps.md` is the one that earns its keep. Everything in it cost real debugging time.
## Defaults
```
Astro static → Cloudflare Workers → worker assets (R2 past ~15 MB)
→ PagesCMS → Brevo → KV → GA4 or Cloudflare Web Analytics → Turnstile
```
Chosen because they are light, cheap at this scale, and compose without glue. Every one has
alternatives in `stacks.md` with the specific condition that would justify switching.
## The template
**It arrives with no palette, no typeface and no home page.** That is the design, not an
omission: a starter that ships a look gives every site built from it the same one. What it
does ship is the skeleton of the parts that take longest to get right:
- **Media pipeline** — `npm run media`: `sharp` → responsive AVIF and WebP, dimensions manifest
so nothing shifts, favicons rendered from the vector, JPEG social twins only for images used
as `og:image`. AVIF is 26% smaller at better quality, measured rather than asserted
- **Design tokens** — colour, fluid type scale, spacing, radii, tinted shadows, motion; plus
a semantic layer so `.on-dark` inverts a whole section by redefining six variables. The
brand ramp is a grey placeholder behind an `--unset` marker; you decide it
- **`npm run tells`** — the checkable half of `design.md` §3 against your own work: measure,
section rhythm, the three-card grid, face pairing, headline size, motion duration, focus
ring, raw hex in components. No browser, so it runs in CI. `build:production` refuses while
anything is left undecided
- **Form endpoint** — server-side validation with field errors, honeypot, KV written
**before** the email provider is called, and a no-JavaScript path that still works
- **Token-protected CSV export** of leads
- **Environment derivation** — one build variable decides noindex, analytics, canonical host,
which store leads land in, and who gets notified
- **Build guard** that refuses *any* build without an environment, because a bare build
publishes `localhost` canonical tags cleanly and with no error. It keys on the command, not
on `CI` — `npm run build` is the most reflexive command in npm, and that is exactly where the
hole was
- **SEO** — canonical, OG, JSON-LD built from the same data file the UI reads
- **Header, footer, contact form, page opening, CTA** — structure and behaviour, not styling:
a mobile menu that replaces the header rather than layering under it, a focus trap, a sticky
close control, and every interactive state a control needs
- **Accessibility statement page** — a required published artefact under the EAA and PSBAR —
plus `npm run a11y` over one URL per template family, **in both colour schemes**, because a
light and a dark palette are two sets of contrast pairs and measuring one proves nothing about
the other. ⚠ It verifies that Chrome actually answered `prefers-color-scheme` the way it was
asked before it measures anything — the previous forcing flag was one Chrome silently ignores,
so the run measured a single palette twice and reported two
- **`npm run reflow`** — WCAG 1.4.10 and 1.4.4 against the **deployed** site: 320px wide and
text at 200%, naming the widest offending element rather than reporting the page as broken.
It is what keeps the testing claim on `/accessibility` honest — on one build that sentence was
true when written and false a day later, because a redesign rebuilt every route
- **`npm run a11y:evidence`** — the dated evidence pack, written to a file instead of dying with
the terminal scrollback. It writes the manual layers in as **unchecked** every time, because
automated tooling catches roughly a third of issues and a pack that listed a clean run and
stopped would read as a finished audit
- **`npm run recon`** — inventory the OLD site before designing routes: URLs from the sitemap
plus the ones only the Wayback Machine remembers, which sitemap filename is canonical,
preserved paths, and the integrations in the markup
- **`npm run extract`** — turns the captured HTML into clean markdown, one file per page, with
frontmatter and portable image paths. It uses a real HTML-to-markdown converter rather than
stripping tags, because `html.replace(/<[^>]+>/g, '')` glues a heading to its paragraph
wherever the source was minified — right on the pretty-printed pages, wrong on the rest. It
also flags what needs a person: heading levels the page builder used as type styles, alt text
generated from the filename, and pages thin enough to suspect the capture
- **`npm run redirects`** — proposes a redirect map from the old inventory against the new
routes, and **never writes `public/_redirects`**. Slug similarity is a guess, and a wrong 301
is worse than a 404: the 404 turns up in the log and gets fixed, the wrong redirect looks
like it works
- **`npm run verify`** — the runbook's verification matrix as a gate that exits non-zero, run
against the **deployed** site. Routes, a real 404, every redirect rule and whether its target
resolves, security headers, the form submissions the API is meant to refuse, plus page weight
and render-blocking counts — bytes and counts, never a timing, because a hand-rolled number
from one machine disagrees with Lighthouse and nothing tells you it is wrong. It crawls every
internal link **and asks the inverse**: a route that nothing links to returns 200, sits in the
sitemap, gets indexed, and cannot be reached by clicking — which is what a rewritten nav leaves
behind
- **The scaffolder stamps `websiteBuildKit.version` into the new site's `package.json`.** The
template is copied, not linked, so nothing the kit fixes afterwards reaches a site already
built — and without a stamp, *"is this site current?"* is archaeology against a repo whose
history you have to guess at
- **Navigation can be a CMS field**, because `check:cms` resolves every internal path in
CMS-managed data against the routes in `src/pages` before the build. The rule used to forbid it —
a typo'd path renders perfectly and 404s only for a visitor — and the result was navigation
missing from **all five audited sites**. Verifying beats forbidding
- **`npm run check:redirects`** — a redirect map that is silently wrong. A duplicate source
(Cloudflare takes the **first** match, so the later rule never fires), a self-redirect, a loop
written across trailing-slash forms, an unsupported status. None of these stop the file parsing
or the site building; they break a URL that used to rank, weeks later, in somebody else's
analytics
- **`npm run check:alt`** — alt text read as **text**. axe and `Img.astro` prove the attribute
exists; nothing anywhere read what it said, so `alt="DSC_0421.jpg"`, `alt="image"`,
`alt="Photo of a kitchen"` and nine photographs all reading `alt="Our work"` passed every gate
the kit has while telling a screen-reader user nothing. It reads `dist/`, because alt arrives
from `.astro`, markdown, a content collection **and a CMS field a client types into** — which is
exactly why the value is weak — and the built page is the only place all four can be read at
once. ⚠ **Few rules, deliberately.** Alt text is prose and a check that argues with a real
sentence gets switched off: `alt=""` is never a finding, "Screenshot of…" is not "Photo of…", and
a picture used twice may share its description. Warns on staging, refuses on production
- **`npm run check:contact`** — the number a visitor **taps** against the number they **read**.
A client's number changes, you update the one you can see, and every `tel:` link keeps dialling
the old one: the page is right in every screenshot, types and axe are clean, and `verify` skips
`tel:` because a protocol handler is not a page. **The only person who finds out is the customer
whose call went nowhere.** `business.phone` now derives `href` and `sms` from one constant, so
three of the four copies cannot disagree; this checks the pair that cannot be derived — locale
formatting against E.164 — and ⚠ **it knows a UK trunk zero is not a mismatch**, because a check
that is wrong about a whole country gets switched off
- **`npm run check:drift`** — what a **delivered** site is behind on. Every other check here
protects the next project; the template is copied, not linked, so a shipped site never receives
any of them. Eleven rows — AVIF, HEIC, a pipeline that drops files silently, hardcoded images,
binary source files, the CMS checks, the client guide, and an accessibility run that measures one
palette twice — reported and never fixed, because drift is a
decision waiting to be made, not an error. `--json` for many sites at once. The repair procedure
lives in [site-runbooks](https://github.com/nurkamol/site-runbooks), which calls this rather than
restating it
- **`npm run check:contrast`** — text on a photograph, measured off the generated pixels. ⚠ **No
accessibility runner can see this failure**: axe and pa11y report a flat ~1.01:1 for every one,
because neither composites a transparent element over the image behind it. Measuring it dissolves
the false choice between letting a client swap a header photo and refusing to let them choose.
Production only, and a no-op until a project declares regions — a region is a box, a scrim and a
text colour, all of which are design, and the template has none
- **`npm run check:binary`** *(kit repo)* — a tracked source file that git treats as **binary**.
One script used a literal NUL as a string sentinel, which made it invisible to `git diff`, to
`grep`, and — the part that matters — **to the provenance sweep, which is written with `grep -I`**.
It carried a client's entire brand for two commits. The obvious implementation is a check that
always passes, so this one is tested in both directions
- **`npm run check:cms`** — a CMS config that will silently destroy content. **A CMS rewrites the
whole file from its schema, so any key the schema does not declare is gone after the client's
first save** — not merged, not flagged, and in the diff it reads as an ordinary content commit.
Audited across five shipped sites, **all five failed**: one was dropping every analytics ID and
the opening hours the moment the client saved Site Settings, another lost its homepage images in
three languages, and two pointed uploads at `public/img` — the media pipeline's *output*. A
no-op when there is no `.pages.yml`. **`npm run check:cms -- --fix` prints the field
declarations to paste**, with every type read off the value actually stored at that path rather
than guessed from the key — and it flags the keys where *moving them out of the CMS* is the
better answer, because for an analytics ID it is. It prints; it never writes. It also **warns** — never fails — about content in the repo
that no CMS entry points at (*"whole sections are missing"* is how a client reports that; one
audited site had an entire services collection it could not edit) and about fields that look
like technical configuration rather than content
- **`npm run check:form`** — two form controls sharing a `name`. **The honeypot is called
`company`**, and `api/contact.ts` discards any submission that fills it in — silently, with a 200
and a thank-you page. Add a real Company field, as a B2B site eventually does, and every enquiry
from a company that types its name is thrown away. Runs before the build, in both environments
- **`npm run check:copy`** — the markers you leave for yourself, found in the text a *reader*
would see: `TODO`, `⚠ CONFIRM:`, `Lorem ipsum`, an unrendered `{{ placeholder }}`. Warns on
staging, refuses on production. It reads rendered text rather than source, so a `TODO` in a code
comment or a `<script>` is ignored and "please confirm your email address" is not a hit — the
exclusions are what stop it becoming a check people skip
- **`npm run check:secrets`** — compares the secrets your code declares in `.dev.vars.example`
against what the deployed worker actually holds, and runs automatically at the end of
`deploy:staging` and `deploy:production`. A missing one never throws: `secret()` returns
`undefined`, the form still validates, still stores the lead, still returns 200 and still
thanks the visitor — it just emails nobody. That is weeks of enquiries found by someone
asking why the phone stopped ringing
- **`npm run shots`** — before/after screenshots of a migration at a mobile and a desktop width,
paired into one page. Both sides read the same URL inventory, so a page that did not survive
shows as a 404 beside its old screenshot. Take the before pass while the old site is still up;
once DNS moves it is gone
- **Legal pages as content** — `src/content/legal/*.md` served through one route, with the
footer links derived from the collection itself. A footer link to a page nobody has written
yet is a 404 on every page of the site, and nothing reports it
- **`npm run console`** — console errors and failed requests on the deployed site, in a real
browser. `verify` uses only `fetch` and so can never see this: the page returns 200, the HTML
is correct, and something inside it failed after the response was complete. A blocked
third-party script, a CSP violation or a 404 asset is invisible to a status-code sweep
- **Build guards** — `check-env` refuses a build whose environment disagrees with the routes in
`wrangler.jsonc`; `check-sitemap` fails a production build when a URL is both in the sitemap
and `noindex`
- **`npm run dns`** — snapshot the zone before you move the apex, and diff it after. Moving a
domain without carrying **MX kills the client's email**, and it fails silently: the bounces
go to the sender, so the people who find out cannot tell the client
- **Staging is marked and blocked** — a standing badge on every non-production build that reads
the live DOM and alarms when the page disagrees with its environment, plus `X-Robots-Tag` on
every response, which is the only one of the three controls that covers a PDF
- **`npm run cards`** — one 1200×630 JPEG Open Graph card per page, picked up automatically
from the canonical path. JPEG because Facebook and LinkedIn still fail to render a WebP
`og:image` and no scraper accepts SVG — a shared post would unfurl with no picture at all.
The generator ships; the card design is a stub, so it renders nothing until you decide it
- **`npm run lastmod`** — per-route content dates for the sitemap, computed by a human with a
full clone and **committed as data**. The first version read `git log` during the build and
emitted nothing in production, because Cloudflare shallow-clones and `git log` returns one
grafted commit for every file
- **`npm run indexnow`** — submit changed URLs to Bing, Yandex, Seznam and Naver. It prints on
every run that **Google does not participate**, because reading a green result as "submitted
to search engines" is how a site goes weeks with nobody asking why Google has not picked
something up
- **`npm run seo`** *(optional)* — [full-site SEO audit](https://www.npmjs.com/package/@nurkamol/seo-audit),
most useful as a **baseline**: capture the old site before migrating, then fail only on
findings new since. Metadata parity is what a rebuild loses quietly
- **Lead retention** — a KV `expirationTtl` from one number in `site.ts`, because "indefinitely"
is not a retention period
- **`docs/`** — runbook (verification matrix, go-live order, first-week watch), content guide,
analytics (the rules whose failure looks like success), a traps file seeded with this
codebase's silent failures, and `handover.md` — the one document written for the client
rather than a developer, rendered to PDF by `npm run handover`
```bash
npm create website-build-kit@latest my-site
```
It checks your Node version before writing anything, restores the `.gitignore` npm strips from
published packages, names the project after the directory, and installs. `npx degit
nurkamol/website-build-kit/template my-site` still works if you would rather not run a
scaffolder.
Node 22.12+. It builds green with no content, no images and no secrets. Fill in
`src/data/business.ts` first — everything else reads from it. Then `npm run tells` will tell
you what is still undecided.
## The two CLAUDE.md files
They do different jobs and neither replaces the skill.
| File | Applies when | Says |
| --- | --- | --- |
| `CLAUDE.md` | You open **the kit** in Claude Code | House style, where things go, the bar for adding a trap |
| `template/CLAUDE.md` | You open **a site built from it** | Tokens not hex, `business.ts` is the single source, use `wrangler dev` for API routes, read `docs/traps.md` first |
The skill is what teaches the *method*. These are what stop a future session breaking the
conventions once the method has been applied.
## Growing it
New prompts, skills and template pieces are meant to accumulate. See
[docs/adding-to-the-kit.md](docs/adding-to-the-kit.md) for the bar, and
[docs/roadmap.md](docs/roadmap.md) for what is queued — including what was
**deliberately rejected**, so it does not come back as a new idea.
The bar for adding a trap: **it failed silently on a real build.** Not "this is good
practice" — something that looked fine and was not.
## Licence
[MIT](LICENSE). Use it, fork it, build client sites with it, sell the result.
**Sites built from it carry no obligation.** The `template/` contents are meant to be copied
into client projects, and MIT does not follow them there in any way that matters: no
attribution in the finished website, no restriction on the deliverable. A licence on the
toolchain that encumbered client work would make the toolchain unusable for the work it exists
for.
The one thing worth asking: if the method here is useful, say where it came from. That is a
request, not a term.
## If you already built a site with this
⚠ **The template is copied, not linked. Nothing the kit fixes afterwards reaches a site already
built.** Not a trap, not a gate, not a pipeline improvement. Every check listed above protects the
next project and none of the ones you have shipped.
That is a deliberate trade — the client owns a self-contained repository that will still build in
five years, with no dependency on this one — and it has a cost that is easy to forget because
nothing reports it.
**The cost is not hypothetical.** A site built from an earlier version was serving every image
about **19% larger** than intended for weeks after the pipeline gained AVIF, and it surfaced only
because someone happened to read both trees side by side for an unrelated reason. The same site
had never received a second fix either: a source file carrying a literal NUL, which `git diff`
shows only as `Binary files differ` and which the kit's own provenance sweep — written with
`grep -I` — cannot see at all.
⚠ **Neither was found by looking. Both were found by accident, months later.** The instinct on
reading this is *I would have noticed*; nobody did, on a site being actively worked on.
**Which version a site came from** is recorded in its `package.json`, for anything scaffolded from
0.1.16 onward:
```json
"websiteBuildKit": { "version": "0.1.15", "scaffolded": "2026-08-31" }
```
Sites older than that carry no stamp; compare `scripts/` against a release by hand.
**What to do about it, today.** Run **`npm run check:drift`** — or copy `scripts/check-drift.mjs`
and `scripts/lib/` in first, if the site predates it. It reads only, and reports the clean rows as
well as the drifted ones. **`/media-audit`** runs it and reports; the repair procedure — what to fix
first, what to leave, and what to ask the client — lives in
**[site-runbooks](https://github.com/nurkamol/site-runbooks)**.
**→ [docs/how-to-repair.md](docs/how-to-repair.md)** — how to begin, if you have never run any
of this: the five-minute setup, one site end to end, and the four decisions you will hit.
The **`site-repair` skill** fires on the situation rather than a command, so *"the client says
they cannot change the photo on their homepage"* reaches it without anyone remembering a name.
It audits read-only first and changes nothing until the findings have been read.
**→ [docs/auditing-a-shipped-site.md](docs/auditing-a-shipped-site.md)** — the whole procedure for
a live site: pointing the current checks at it without copying anything in, what every CMS and
media finding means, the order to repair them in, and how to prove you changed only what you meant
to.
⚠ **That split is deliberate.** An executable check belongs here, where `test:gates` proves it
still refuses and it ships into every project. A repair procedure is long-form judgement that
cannot be tested, and keeping a second copy of one here is how the two drift apart while nothing
says so — which is the failure this whole section is about.
⚠ **You do not have to copy anything in.** Every check resolves paths from the **current working
directory**, so a clone of this repository can audit any project in place — including one built
before these checks existed:
```bash
cd path/to/the-site
node path/to/website-build-kit/template/scripts/check-drift.mjs # what it is behind on
node path/to/website-build-kit/template/scripts/check-cms.mjs # content the CMS deletes or cannot reach
node path/to/website-build-kit/template/scripts/check-redirects.mjs # a map that is silently wrong
node path/to/website-build-kit/template/scripts/check-contrast.mjs # text on photographs
```
Verified against a shipped site carrying **none** of the four scripts: all ran, and `git status`
reported **0 changed files**. That matters twice — you are not committing tooling to a client's
repository to read it, and you are running the **current** checks rather than a copy that has
itself gone stale.
Copying them in is still an option if you want them in that project's own build. A wholesale copy
of `template/` over a live site is not: it overwrites the design that makes it that client's site.
The checks are the portable part:
```bash
npm run check:cms # a CMS config that deletes content on the client's first save
npm run check:form # a real field colliding with the honeypot
npm run check:copy # notes to yourself, shipped as body copy
npm run tells # what is still undecided, and the generated-site tells
```
⚠ **Expect them to fail.** Five shipped sites were audited with `check:cms`; **all five failed**,
and two were losing client data — one deleted every analytics ID and its opening hours the moment
the owner opened Site Settings. That is what a gate arriving late looks like, and it is the
argument for running them rather than assuming.
## Provenance
Sharpened on getmiohome.com and expressducttest.com, both WordPress rebuilds onto Astro +
Cloudflare Workers. Client names and project detail in this repository are shared in
confidence; contact details, credentials and analytics IDs are not permitted here at all —
`CLAUDE.md` carries the sweep that enforces it.
Explore similar templates
Compare previews and descriptions within the same stack and category.

astrowind
AstroWind is a free, open-source Astro starter theme using Astro and Tailwind CSS, released under the MIT license.
Astro · Blog
accessible-astro-starter
An accessibility-focused Astro 6 starter template with WCAG 2.2 AA practices, Tailwind CSS 4, TypeScript, Prettier, ESLint, form validation, blog and portfolio examples, and preference toggles for dark mode, high contrast, and reduced motion.
Astro · Blog

astro-erudite
astro-erudite is an opinionated, unstyled static blog template built with Astro and native CSS, with no UI or CSS framework and minimal dependencies.
Astro · Portfolio

astro-landing-page
An Astro + Tailwind CSS landing page example template with theme variables, dark mode, responsive layout, and Open Graph tags, under the MIT license, suitable as a starting point for single-page product, event, or corporate homepage sites.
Astro · Corporate Website

astro-shopify
A lightweight Shopify headless ecommerce starter template built with Astro and Svelte, integrating the Shopify Storefront API for product and inventory data, with built-in cart state management and basic add-to-cart and checkout flow.
Astro · E-commerce

brutal
Brutal is a neobrutalism Astro starter theme from ElianCodes, derived from the author's personal site and generalized. It includes a Markdown blog collection, build-time RSS and sitemap, image optimization, Tailwind CSS styling, and Biome checks, with no JavaScript by default.
Astro · Starter
