AstroWordPress企业官网启动器

website-build-kit — Astro 企业官网模板

作者 nurkamol

website-build-kit 是一个用于构建和交付生产营销网站的方法与起始模板,包含 Astro + Cloudflare 技术栈、技能插件和检查工具,强调验证部署后的实际结果。

看看你的网站会是什么样

浏览真实页面截图,比较内容排版与手机效果。点击下方缩略图切换,放大查看整页细节。

2 个预览视图
桌面端首页1 / 2
桌面端截图首屏预览

由服务器浏览器实际访问页面后截取。

全部页面与视图 2

截图用于了解视觉与布局,交互效果请以实际项目为准。

采集于 2026-10-02查看来源

值得关注的模板亮点

从已有项目资料中,快速了解它的特点。

01

提供 Astro + Cloudflare 起始模板,包含媒体管道、设计令牌和表单端点等骨架。

02

技能插件通过描述匹配触发,支持 /website-build 等命令。

03

内置多个验证脚本,如 npm run verify 和 npm run a11y,针对部署后站点进行检查。

04

默认技术栈包括 Astro 静态生成、Cloudflare Workers、PagesCMS、Brevo 等。

05

模板不预设视觉设计,避免所有站点外观雷同。

模板详细介绍

website-build-kit 提供了一套从发现到交付的营销网站构建方法,以及一个 Astro + Cloudflare 起始模板。README 说明其价值在于积累的决策和静默失败清单,而非主题。模板不预设调色板、字体或首页,但包含媒体管道、设计令牌、表单端点、SEO 和多个验证脚本。技能通过描述匹配触发,无需记忆命令。README 也提供了安装和使用方式,以及详细的参考文档。

AstroWordPress企业官网启动器

适合你的项目吗?

适用场景

  • 将现有网站(如从 Elementor)迁移到 Astro 和 Cloudflare。
  • 为没有现成网站的企业(如牙科诊所)从零构建营销网站。
  • 对已交付但出现漂移的网站进行修复和维护。
  • 在 CI 中运行自动化检查,确保部署后的网站符合预期。

适合人群

  • 需要构建生产营销网站的开发团队。
  • 希望采用结构化方法避免静默失败的开发者。
  • 使用 Astro 和 Cloudflare 技术栈的团队。
  • 需要无障碍和 SEO 检查的网站项目。

上手与定制指南

结合仓库当前版本的说明,按下面的资料检查内容、配置与运行要求。

  1. 1

    通过 /plugin marketplace add nurkamol/website-build-kit 和 /plugin install website-build@website-build-kit 安装插件。

  2. 2

    或克隆仓库并运行 ./install.sh 将技能和命令符号链接到 ~/.claude。

  3. 3

    使用 npm create website-build-kit 创建新项目。

  4. 4

    在项目中直接描述需求即可触发技能,或使用 /website-build <url> 命令。

查看仓库使用说明

授权与来源资料

使用条件与限制

免费使用 · 保留许可声明

本次收录的是公开仓库中采用 MIT 许可的免费版本;免费范围为该版本代码,不代表外部服务或第三方素材免费。

使用时需要遵守

  • 允许免费使用、修改、商用和分发该版本代码。
  • 分发代码副本或实质性部分时,保留原作者版权声明和完整 MIT 许可声明。
  • 代码按现状提供;担保与责任限制以原许可证及适用法律为准。

额外费用与待核对事项

  • 演示图片、字体、图标及其他第三方素材需按各自许可核对,不能仅凭代码的 MIT 许可判断。
  • 域名、托管、数据库、支付或 AI 等外部服务可能另外收费。
  • 模板要求 Node 22.12+ 环境。
  • 默认技术栈依赖 Cloudflare Workers、PagesCMS、Brevo、KV、GA4 或 Cloudflare Web Analytics、Turnstile 等外部服务。
  • 表单邮件等功能需要配置密钥(.dev.vars.example 中声明的 secrets),缺失时表单仍返回成功但不会发送邮件。
  • npm run seo 为可选功能,依赖外部 npm 包 @nurkamol/seo-audit。
  • 站点修复流程文档位于独立仓库 site-runbooks,而非本仓库内。
  • 仓库中客户名称与项目细节为保密共享,联系方式、凭据和分析 ID 不允许出现在仓库中。
查看原文依据

Permission is hereby granted, free of charge

许可证原文

Node 22.12+. It builds green with no content, no images and no secrets. Fill in

README 原文

Astro static → Cloudflare Workers → worker assets (R2 past ~15 MB)

README 原文

- **`npm run check:secrets`** — compares the secrets your code declares in `.dev.vars.example`

README 原文

- **`npm run seo`** *(optional)* — [full-site SEO audit](https://www.npmjs.com/package/@nurkamol/seo-audit),

README 原文

lives in [site-runbooks](https://github.com/nurkamol/site-runbooks), which calls this rather than

README 原文

Cloudflare Workers. Client names and project detail in this repository are shared in

README 原文
https://github.com/nurkamol/website-build-kit/blob/master/LICENSEhttps://github.com/nurkamol/website-build-kit/blob/master/README.md

DeepSeek 根据仓库许可证和 README 整理 ·2026-10-04。结果仅覆盖已读取资料,依赖、图片、字体及后续条款变更以各自原文为准。

项目核对记录与介绍依据

模板资料

来源平台
GitHub
资料与维护参考分
资料较完整 · 92 / 100
技术栈
Astro · WordPress
网站类型
企业官网 · 启动器
这个分数如何计算

本站按许可资料(最高30分)、演示链接(12分)、预览图(10分)、Star数(15分)、记录的维护时间(15分)、描述完整度(8分)、功能记录(5分)和分类资料(5分)计算。它不是代码、安全或性能测试,也不是 Google 评分;有链接不代表已实测可用。

这个项目需要核对什么

记录的仓库更新时间
2026-09-30
截图检查日期
2026-10-04

已收录演示链接。请用你的内容与屏幕尺寸检查;有演示不代表表单、认证或支付已经可用。

目录记录的主要技术栈是 Astro。请通过依赖清单和锁文件核对实际版本与托管要求。这些说明基于收录资料,不代表代码审计或部署实测。

查看具体选型方法 →

介绍的制作与核对状态

介绍由 AI 辅助整理目录与仓库资料。仓库作者不等于这篇介绍的审核者。

审核声明: codex-user-requested-batch-review-20261017 · 2026-10-16

查看当时使用的来源资料

SHA-256: eb7d0378efaace196738a9787a6cf405b4761bee6defb1380dc7074b6de55ed5

2026-10-04T20:30:28.447Z

来源文件 / 仓库
<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.
反馈内容错误

看看同类模板

对比同技术栈与用途的模板,找到更适合的视觉与内容结构。