Safe content refresh
How a snapshot site takes new content without breaking the code you wrote. Covers the site snapshot, which of your files read which keys, and what to do when a refresh breaks something.
On this page
- The snapshot site
- Layer 1: who reads which key
- Layer 2: the content report
- Layer 3: the check fails loudly
- Layer 4: the route snapshot
- What to do when a refresh breaks something
- A key is gone
- A route disappeared, or a link now leads nowhere
- A heading or an asset is gone from a page
- Generated copy is stale
- The primary call to action lost its target
- You do not want the refresh
- Make your code refresh-proof
- What can go wrong
- What’s next
Editors keep publishing. Your hand-written pages and components read that content. A refresh moves the site to newer content, so the question is what it does to your code. The builder answers it in three layers: it finds the files that read each key, it fails loudly when a key is gone, and it compares the routes of the old and the new build. You decide at each step, and nothing moves before you approve.
This page explains the layers and what to do when a refresh breaks something. The steps of the refresh itself are in Refresh content.
The snapshot site
A site in snapshot mode builds against one lake commit, which lake.lock pins. Publishing in the CMS does not change the site. The pin moves only through the refresh-content skill. That gives you a fixed point to compare against, and it means your code never meets new content by surprise. See How sites work.
Note
A live site follows the newest commit on its own and has no refresh step. A missing document then breaks the build the next time it runs. Prefer snapshot when hand-written pages read single documents.
Layer 1: who reads which key
Your pages and components read content through getLakeDocument, getLakeCollection and getLakeImage, with a string key. The tools scan the site-owned files under src/ for these calls. Kit and generated files are not scanned. Adopted files are, because they are yours now.
The result is a map from key to files. A slot counts under its own name and under the key it is bound to. A change to the document bound to pricing is also a change to the slot pricing.
The scan is static. It reads the text of the call, so it needs a string literal as the key:
| Call | Tracked |
|---|---|
getLakeDocument('pricing') | Yes |
getLakeDocument(name) | No. check-bindings warns, and refresh-content cannot name the file. |
getLakeDocument(`plan-${n}`) | No. Same warning. |
A match inside a comment counts as well. The tool prefers a spurious name to a missed file.
Layer 2: the content report
refresh-content compares the lake at the locked commit and at the newest one, for this site only. For every changed, unpublished or deleted key it lists the files that read it.
src/pages/pricing.astro reads pricing, which was removedYou see this before the candidate is built. It tells you which of your files to open first, and which edits are only informational (a new item in a list).
Layer 3: the check fails loudly
With the candidate commit, npm run check fails when a site-owned file reads a key whose document is missing. It never hides the problem and it never falls back to old content.
error documents.pricing: Document 01JBXQ3F7M2N8P4R6S9T1V5W0A is missing or not published (key "pricing"); read by site code: src/pages/pricing.astroThe message gives the binding, the document ID, the key and the site files that read it. A collection that has no published documents is a warning (“Collection has no published documents”), and an error only when it fills a required slot of the site type. Your code must cope with an empty collection.
Layer 4: the route snapshot
site-snapshot.json is committed. npx mdlake snapshot writes it from a build. It records, per route, the title, the heading outline, a hash of the visible text, the internal links, the broken links and the asset IDs. It holds nothing random, so the same site gives the same file.
refresh-content builds the candidate and runs npx mdlake snapshot --compare against it. The report says which routes were added or removed, which headings were lost, which links now lead nowhere and which assets are gone. This catches what the type system cannot: a page that still builds but now shows less than before.
npm run check warns when site-snapshot.json is missing or older than lake.lock. Fix it with npm run build and then npx mdlake snapshot. Commit the file with the lock.
What to do when a refresh breaks something
Work through the cases in this order.
A key is gone
A document that your page reads was unpublished or deleted.
- Ask the editor whether it was on purpose. If not, ask them to publish it again. Then run the refresh again.
- If it was on purpose, pick a fix in your file:
| Fix | When |
|---|---|
Bind another document to the same key in lake.config.ts | A replacement exists. |
| Handle the missing case in the page | The content is optional. |
| Remove the page, or the section that used it | The content is gone for good. |
| Change the collection query | A list lost an item that you addressed by key. |
- Edit your site-owned file. If the binding is in
lake.config.ts, changeplan.mdand runplan-to-config.mjs, or adopt the file. See Ownership zones. - Run
npm run checkagain withLAKE_LOCK_CANDIDATEset. See the command in Refresh content.
The skill proposes such a fix and applies it only after you agree. It never edits the lake.
A route disappeared, or a link now leads nowhere
The route comparison names them. A page of the plan may have lost its document. A link in a document may point at one that is no longer published, and the kit turns a link without a page into plain text.
- Restore the document, or
- change the plan so the route goes.
A heading or an asset is gone from a page
Usually an editor rewrote the document. Check whether the loss is wanted. If not, ask the editor. If your component expects a heading by name, make it tolerant, because editors will change headings.
Generated copy is stale
A source of a generated file changed. The report lists the file as stale. Regenerate it through generate-copy, or revert the edit in the lake. See Generated copy.
The primary call to action lost its target
The brief says which page the call to action leads to. A refresh that removes that page is reported as a conflict before the stop. You decide, and the brief is updated in the same change when the answer changes.
You do not want the refresh
Reject it at the stop. The lock and the snapshot stay as they are, the skill puts the preview back, and the site is unchanged. Nothing is lost.
Make your code refresh-proof
- Use string literals as keys.
- Bind documents by key in
lake.config.tsso a replaced document is one change. - Prefer a collection to a list of single documents when the number of items changes.
- Treat optional fields as possibly missing, and render nothing for a missing one.
- Do not copy lake text into code. A copy hides the change from every check above.
- Commit
site-snapshot.jsonwith every lock change. - Run
npm run checkbefore you merge the change that moves the lock. The generated pipelines run it on pushes tomain, not on pull requests, so a pull request check needs a pipeline you add yourself.
What can go wrong
| Symptom | Cause and fix |
|---|---|
| A file that reads a changed key is not named | The key is not a string literal. Change the call. |
| “No committed site-snapshot.json” | Build at the locked commit, run npx mdlake snapshot and commit. |
| The check passes and a page looks wrong | The key still exists but its content changed. Read the route comparison and the preview. |
| The check fails only with the candidate | That is what it is for. Fix the file that it names. |
What’s next
- Refresh content for the steps and the approval stop.
- Custom pages for how to read content in a way the tools can track.
