|
|
|
@@ -296,7 +296,52 @@ Three packages in order, and the order is a dependency chain rather than a habit
|
|
|
|
|
`shared` emits the types and the warning copy both other packages import, `api` compiles
|
|
|
|
|
`api/src` to `api/dist`, and `web` prerenders against a running API.
|
|
|
|
|
|
|
|
|
|
Output lands in `api/dist/` and `web/dist/`. Every page is prerendered once per language, so ~55 mints
|
|
|
|
|
Output lands in `api/dist/` and `web/dist/`.
|
|
|
|
|
|
|
|
|
|
### Live lists
|
|
|
|
|
|
|
|
|
|
The prerendered mint list is a snapshot of what `GET /api/mints` said when the build ran.
|
|
|
|
|
It used to stay that until the next build, which is why there was a nightly timer: a mint
|
|
|
|
|
indexed at noon was reviewable at once — the 404 resolver saw to that — and had no card on
|
|
|
|
|
`/mints` until 03:30, beside cards whose ratings and statuses were equally old.
|
|
|
|
|
|
|
|
|
|
`/mints`, `/fedimints`, `/lnurl-mints` and the home page's three top-six strips now refetch
|
|
|
|
|
that endpoint once, after paint, and rebuild their grids from the answer. One request per
|
|
|
|
|
page, no relays involved: card counts have always come from the API's ingested review
|
|
|
|
|
aggregates, and review *bodies* remain a mint page and `/reviews` concern.
|
|
|
|
|
|
|
|
|
|
**The prerendered cards stay.** They are the first paint, they are what a crawler indexes,
|
|
|
|
|
and they are the whole page for a reader with no JavaScript — `<noscript>` already unhides
|
|
|
|
|
them, and the search, sort and filter controls act on whatever is in the DOM. Hydration
|
|
|
|
|
only ever replaces them with something newer, and never with nothing:
|
|
|
|
|
|
|
|
|
|
- a failed fetch does nothing at all, silently — a grid that is correct as of the last
|
|
|
|
|
build is a far better answer to a flaky network than an error about a list already on
|
|
|
|
|
screen;
|
|
|
|
|
- an API answering `[]` also does nothing. Serving an empty index is the failure the
|
|
|
|
|
[build gate](#rebuilds) exists to catch, and a page that rendered it as "no mints" would
|
|
|
|
|
be that bug wearing a different hat;
|
|
|
|
|
- whatever the reader had already set — a search they typed, a sort they picked, "hide
|
|
|
|
|
offline" — is re-applied to the new cards, so a refresh landing mid-interaction cannot
|
|
|
|
|
undo it.
|
|
|
|
|
|
|
|
|
|
Two pieces make it work. `web/src/lib/mint-cards.ts` is `MintCard.astro`'s browser twin,
|
|
|
|
|
the same relationship `review-cards.ts` has with the reviews panel: identical classes and
|
|
|
|
|
identical `data-*` attributes, because the sort keys, the search fields, the rank chips and
|
|
|
|
|
the shared-element view transitions are all read off the DOM. And `MintListItem` carries
|
|
|
|
|
the facts a chip is drawn from — `nuts`, `capabilities`, and the LNURL withdraw ceiling and
|
|
|
|
|
funding flag. Those replaced an N+1: `/mints` and `/lnurl-mints` each used to fetch
|
|
|
|
|
`GET /api/mints/:host` once per mint at build time to read two booleans off it, which is
|
|
|
|
|
tolerable on a build machine and unthinkable in every visitor's browser.
|
|
|
|
|
|
|
|
|
|
Strings come from the page's own inlined catalog, so a hydrated Spanish card says "En
|
|
|
|
|
línea", "54 reseñas" and "4,9", and its link is `/es/mint/…`. The one thing hydration
|
|
|
|
|
cannot improve is the home page's sentiment bars: the build gives those six cards real
|
|
|
|
|
rating distributions from a per-mint detail fetch, and the list payload has no
|
|
|
|
|
distribution in it, so a refreshed card falls back to the rating proxy the index pages
|
|
|
|
|
have always used.
|
|
|
|
|
|
|
|
|
|
Every page is prerendered once per language, so ~55 mints
|
|
|
|
|
and 9 static routes come out as ~200 pages, each with real titles, meta descriptions,
|
|
|
|
|
OpenGraph and Twitter tags, a social card, a self-referencing canonical, a full hreflang
|
|
|
|
|
set and a JSON-LD graph. `sitemap.xml` lists every indexable one with its `xhtml:link`
|
|
|
|
@@ -1359,10 +1404,36 @@ journalctl -t cashumints-alert -n 5 --no-pager
|
|
|
|
|
|
|
|
|
|
### Rebuilds
|
|
|
|
|
|
|
|
|
|
Mint pages are prerendered, so new mints and new review counts appear at the next build.
|
|
|
|
|
A nightly rebuild is enough; the site stays correct in between because the islands
|
|
|
|
|
refresh status and reviews at runtime, and an unbuilt mint still resolves through the
|
|
|
|
|
client-side fallback on the 404 page.
|
|
|
|
|
**Builds happen on deploy. There is no timer.**
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
sudo systemctl start cashumints-web # build, then publish
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
There used to be a `cashumints-web.timer` firing at 03:30 nightly, and it was load-bearing:
|
|
|
|
|
the mint list was a snapshot of whatever the API held when `astro build` ran, so a
|
|
|
|
|
rebuild was the only way a new mint, a new review count or a changed status ever reached
|
|
|
|
|
`/mints`. The list hydrates now — one `GET /api/mints` after paint, see
|
|
|
|
|
[Live lists](#live-lists) — so all three reach the page within a second of load, in every
|
|
|
|
|
language, and rebuilding 2,000 pages overnight to refresh numbers that refresh themselves
|
|
|
|
|
is twenty minutes of CPU for nothing.
|
|
|
|
|
|
|
|
|
|
What a build still produces, and therefore what a deploy is still for:
|
|
|
|
|
|
|
|
|
|
| Still built | Still stale between deploys |
|
|
|
|
|
| --- | --- |
|
|
|
|
|
| The prerendered HTML a crawler reads | The `ItemList` JSON-LD on the index pages |
|
|
|
|
|
| A social card per mint | The card for a mint indexed since the deploy |
|
|
|
|
|
| `sitemap.xml` and the hreflang set | A `/mint/{host}` page for a mint indexed since the deploy |
|
|
|
|
|
|
|
|
|
|
That last row is the one to know about. A mint indexed today has no prerendered page of
|
|
|
|
|
its own until the next deploy: `/mint/newhost` returns **404**, and the 404 page's
|
|
|
|
|
resolver looks the address up against the live API and renders it — readable, reviewable,
|
|
|
|
|
linkable, with a `noindex` on it until the deploy gives it a real page. That was already
|
|
|
|
|
true between nightly builds; dropping the timer only lengthens the window.
|
|
|
|
|
|
|
|
|
|
Deploy when you ship code, or when enough new mints have accumulated that their pages are
|
|
|
|
|
worth prerendering. Nothing breaks if you do not.
|
|
|
|
|
|
|
|
|
|
Publishing is a separate step from building, and the separation is the point: the copy
|
|
|
|
|
the site server reads is only touched once a build has succeeded, so a failed build
|
|
|
|
@@ -1464,7 +1535,7 @@ ExecStartPost=/usr/bin/rsync -a --delete-after --delay-updates web/dist/ /var/li
|
|
|
|
|
# ~500 prerendered pages plus a card per mint. Minutes, not seconds, on a small VPS, and
|
|
|
|
|
# TimeoutStartSec is what bounds a Type=oneshot.
|
|
|
|
|
TimeoutStartSec=1800
|
|
|
|
|
# A nightly rebuild should not starve the API it is reading from.
|
|
|
|
|
# A build should not starve the API it is reading from.
|
|
|
|
|
Nice=10
|
|
|
|
|
UMask=0022
|
|
|
|
|
|
|
|
|
@@ -1481,22 +1552,23 @@ ProtectControlGroups=true
|
|
|
|
|
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
There is deliberately no `[Install]` section: a rebuild should be scheduled, not fired on
|
|
|
|
|
every boot.
|
|
|
|
|
There is deliberately no `[Install]` section: this belongs to a deploy, not to a boot.
|
|
|
|
|
|
|
|
|
|
```ini
|
|
|
|
|
# /etc/systemd/system/cashumints-web.timer
|
|
|
|
|
[Unit]
|
|
|
|
|
Description=Nightly cashumints.space rebuild
|
|
|
|
|
#### Removing the timer
|
|
|
|
|
|
|
|
|
|
[Timer]
|
|
|
|
|
OnCalendar=*-*-* 03:30:00
|
|
|
|
|
Persistent=true
|
|
|
|
|
On a host that still has the nightly timer installed, once:
|
|
|
|
|
|
|
|
|
|
[Install]
|
|
|
|
|
WantedBy=timers.target
|
|
|
|
|
```bash
|
|
|
|
|
sudo systemctl disable --now cashumints-web.timer
|
|
|
|
|
sudo rm -f /etc/systemd/system/cashumints-web.timer
|
|
|
|
|
sudo systemctl daemon-reload
|
|
|
|
|
systemctl list-timers --all | grep cashumints # expect nothing
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`disable --now` both stops the pending job and removes the `timers.target` symlink;
|
|
|
|
|
without the `rm` and the `daemon-reload`, systemd keeps a unit it can still be asked to
|
|
|
|
|
start by name.
|
|
|
|
|
|
|
|
|
|
### First deploy
|
|
|
|
|
|
|
|
|
|
Order matters once: the site server refuses to start against a root that has no
|
|
|
|
@@ -1507,7 +1579,6 @@ Order matters once: the site server refuses to start against a root that has no
|
|
|
|
|
sudo systemctl enable --now cashumints # API first: the build reads from it
|
|
|
|
|
sudo systemctl start cashumints-web # build, then publish to /var/lib/cashumints/web
|
|
|
|
|
sudo systemctl enable --now cashumints-site # now it has something to serve
|
|
|
|
|
sudo systemctl enable --now cashumints-web.timer
|
|
|
|
|
sudo nginx -t && sudo systemctl reload nginx
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|