Marketing site for the NumeraSheets shop: a home page listing every spreadsheet template, one page per product, and two free calculators. It never takes a payment — buying happens on the shop, and every outbound link is built in one place so it stays trackable.
Next.js 16 (App Router) · React 19 · Tailwind CSS v4 · TypeScript. Type is Archivo + IBM Plex Mono; the palette is the shop's own ink navy and gold.
npm install
npm run devA push to main that touches the site runs .github/workflows/publicar-cloudflare.yml:
npm ci → typecheck → lint → build → wrangler pages deploy out (Direct Upload) to the
existing Pages project numerasheets-site. The build runs on GitHub Actions, not on
Cloudflare, and the Pages project's own automatic builds are switched off in the
dashboard so each push publishes once. It needs two repository secrets,
CLOUDFLARE_API_TOKEN (Pages: Edit only) and CLOUDFLARE_ACCOUNT_ID.
To roll back: Pages dashboard → Deployments → Rollback to this deployment.
Live on numerasheets.com, served by Cloudflare Pages (the site left Vercel on 2026-09-04).
| Host | What happens | Why |
|---|---|---|
numerasheets.com |
serves the site | the canonical host |
www.numerasheets.com |
301 → apex, path and query preserved | measured 2026-09-09; the redirect rule lives in the Cloudflare dashboard, not in this repo — see below |
numerasheets-site.pages.dev |
200 + X-Robots-Tag: noindex |
the Pages host must keep working for deploy previews, so it is not redirected — only kept out of the index. Rule lives in public/_headers |
The canonical tag is what keeps this safe, and it needs no host logic:
SITE_URL in lib/products.ts is absolute, so every page declares
https://numerasheets.com/... no matter which host served it. Verified by
fetching www and pages.dev directly — both return the apex canonical.
Static export has no redirects() and no headers() — there is no server
to run them. Everything that used to live in next.config.ts had to move:
headers became public/_headers, which Cloudflare Pages reads.
Redirects cannot follow the same path: Cloudflare's _redirects file is
path-based only and explicitly does not support domain-level redirects.
A www → apex redirect has to be a Redirect Rule / Bulk Redirect in the
Cloudflare dashboard, not a file in this repo. Do not try to express it in
_redirects; it will silently never match — which is why looking for it here
and finding nothing does not mean it is missing.
It is in place. Measured on 2026-09-09:
https://www.numerasheets.com/templates/debt-payoff-tracker?utm_source=pinterest
-> 301 https://numerasheets.com/templates/debt-payoff-tracker?utm_source=pinterest
Permanent, and it preserves path and query — which is what campaign links
need, since they carry ?utm_source=. Test it from the command line, not the
browser: host-level routing is invisible to a normal page load.
This paragraph used to read "until that rule exists,
wwwis a duplicate host held in check by the canonical tag alone." The rule was created and the warning stayed — an orphaned note, the same failure mode already recorded in.claude/rules/etsy-titulo.md. A note that justifies the absence of something has to be re-read when that something arrives, or the next review spends its time chasing a problem that is already solved.
- Product data lives in
lib/products.ts.listingUrl()is the only place a shop URL is built andproductUrl()the only place a page URL is, so theBUY_ONswitch can move the whole catalogue between shops in one line. - The home page's product count, the price ceiling and the
keywordslist are all derived from that array. Numbers written by hand drift; these have. - The bundle is in
productsbut is not a template. It carrieskind: "bundle", which keeps it out oftemplates— the list the catalogue counts and renders as cards. It still gets a page, a sitemap entry and a keyword, because those come fromproducts. Without the flag the heading would read "Thirteen templates" while listing twelve, counting the eleven inside the bundle a second time. It also renders as its own offer block rather than a card, because a set sells on the saving it names. - Listing images in
public/shots/are renders of the real workbooks, resized from the artwork used on the shop listings themselves. They are generated byProducts/_shared/export_card_shots.pyin the workspace repo, never by hand. app/opengraph-image.pngmust be captured from the deployed production URL, never fromnext dev— the dev server injects a floating indicator.
| Project | What it does |
|---|---|
| numeros-publicos · site | Open data on every Brazilian municipality (IBGE, National Treasury, INEP), checked against the source, one page per municipality |
| simples-nacional-br · site | Brazil's Simples Nacional tax regime checked against the law: rate, DAS, split, Fator R and CNAE |
| linguagem-simples-br · site | Open checker for Brazil's Plain Language Act (Law 15,263/2025): tests a text against its art. 5 |
| claude-context-cost · site | How much each source (CLAUDE.md, rules, skills, memory) costs in a Claude Code session's context |
| vra-anac | On-time performance of Brazil's domestic flights from ANAC's VRA data |
| tts-ptbr | Brazilian Portuguese text-to-speech with open models (Kokoro and Piper), measured by Whisper |
| carrossel-terminal | Terminal-style Instagram carousel: Markdown becomes PNG, checked before posting, and the post becomes a Reel |
| ia-testada | The measurements behind the @peterwk.dev posts, with the script and how to redo them |
None, on purpose. This repository is public so the deploy can be read, not so its parts can be reused: the code, the copy, the images and the NumeraSheets name are all rights reserved.