Skip to content

Build from committed content snapshot instead of live APIs - #6

Merged
Korede-TA merged 3 commits into
mainfrom
feat/content-snapshot-build
Aug 6, 2026
Merged

Build from committed content snapshot instead of live APIs#6
Korede-TA merged 3 commits into
mainfrom
feat/content-snapshot-build

Conversation

@Korede-TA

Copy link
Copy Markdown
Contributor

Notion's private api/v3 and Substack's public API both sit behind Cloudflare, which serves a 403 challenge to GitHub Actions runners. CI could not fetch either, so the site shipped without essays.

This fetches the raw upstream data from a dev machine instead, commits it under data/, and parses it at build time:

npm run fetch-content [-- --only=notion|substack]

Only fetching moves offline — all parsing still happens during the build, so a stale snapshot means stale content, never a stale layout. Builds select a source via CONTENT_SOURCE:

value behaviour
auto (default) use data/ when present, otherwise fetch live — good for local dev
snapshot use data/ only, fail the build if missing or incomplete
live ignore data/ and always fetch

Both workflows set CONTENT_SOURCE=snapshot so a missing snapshot fails loudly instead of silently deploying an empty site.

Two bugs fixed along the way

  • Notion returned 403 to any client without a browser User-Agent, including local dev — this was the failure behind the broken build, not just a CI problem. lib/notion-api.ts now sends browser headers. It also passed NOTION_API_TOKEN (an ntn_ integration token) as authToken, which notion-client sends as the token_v2 browser cookie — the wrong credential type entirely. Now reads NOTION_TOKEN_V2; the root page is public, so no auth is needed at all.

  • notion-client 7.10 returns __version__ 3 recordMaps, nesting entries as {value:{value,role}} instead of {role,value}. react-notion-x unwraps either via getBlockValue, but nine call sites in this repo read .value directly and silently got undefined — post pages rendered an empty <h1> and a date of 12/31/1969. Fixed at all nine sites; the bespoke normalizeMap workaround is gone.

Note on package-lock.json

The large diff is a fix, not churn. The lock still pinned notion-* at ^6.16.0 while package.json had moved to ^7.10.0 — and since the patches are named +7.10.0, they could not have applied to a fresh CI install. Installing tsx forced npm to reconcile it.

Verified

  • Clean next build && next export renders 7 Substack + 6 Notion entries inline in one date-sorted list
  • All 6 post pages render full bodies (4.3k–40.7k chars) with correct dates
  • All three CONTENT_SOURCE modes exercised, including the hard-fail path
  • tsc --noEmit clean on every file touched; patch-package applies cleanly

Known limitation

Notion signs file-attachment URLs with a ~hours expiry, baked in at fetch time. Exactly one rendered link is affected — a PDF inside a collapsed toggle on nigeria-agriculture-research. This is inherent to static export rather than new here (a live build has the same problem, just refreshed more often). Images are unaffected: they route through the notion.so/image/ proxy with unsigned source URLs. Fix, if wanted: download attachments into public/ at fetch time.

🤖 Generated with Claude Code

Notion's private api/v3 and Substack's public API both sit behind
Cloudflare, which serves a 403 challenge to GitHub Actions runners. CI
could not fetch either, so the site shipped without essays.

Fetch the raw upstream data from a dev machine instead, commit it under
data/, and parse it at build time:

  npm run fetch-content [-- --only=notion|substack]

Only fetching moves offline — all parsing still happens during the build,
so a stale snapshot means stale content, never a stale layout. Builds
select a source via CONTENT_SOURCE: auto (snapshot if present, else live),
snapshot (snapshot only, hard-fail if missing), or live. Both workflows
set snapshot so a missing one fails loudly instead of deploying an empty
site.

Also fixes two bugs found along the way:

- Notion returned 403 for *any* client without a browser User-Agent,
  including local dev. lib/notion-api.ts now sends browser headers. It
  also passed NOTION_API_TOKEN (an ntn_ integration token) as authToken,
  which notion-client sends as the token_v2 browser cookie — the wrong
  credential entirely. Read NOTION_TOKEN_V2 instead; the root page is
  public so no auth is needed.

- notion-client 7.10 returns __version__ 3 recordMaps, nesting entries as
  {value:{value,role}} rather than {role,value}. react-notion-x unwraps
  either via getBlockValue, but nine call sites in this repo read .value
  directly and silently got undefined — post pages rendered an empty <h1>
  and a date of 12/31/1969. Use getBlockValue throughout and drop the
  bespoke normalizeMap workaround.

package-lock.json carries a large diff because it still pinned notion-*
at ^6.16.0 while package.json had moved to ^7.10.0 — the patches are
named +7.10.0 and could not have applied to a fresh CI install.

Verified: clean build + export renders 7 Substack and 6 Notion entries
inline in one date-sorted list, and all 6 post pages render full bodies
with correct dates.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@netlify

netlify Bot commented Aug 6, 2026

Copy link
Copy Markdown

Deploy Preview for soft-swan-5d0ce7 ready!

Name Link
🔨 Latest commit 698bef1
🔍 Latest deploy log https://app.netlify.com/projects/soft-swan-5d0ce7/deploys/6a7420de0f562100082cf042
😎 Deploy Preview https://deploy-preview-6--soft-swan-5d0ce7.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify

netlify Bot commented Aug 6, 2026

Copy link
Copy Markdown

Deploy Preview for darling-dusk-441006 ready!

Name Link
🔨 Latest commit 698bef1
🔍 Latest deploy log https://app.netlify.com/projects/darling-dusk-441006/deploys/6a7420de592af60008f97723
😎 Deploy Preview https://deploy-preview-6--darling-dusk-441006.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

The Netlify build failed at `npm install` — patch-package could not apply
either patch, so postinstall exited 1.

Both patches had been generated against locally-linked, locally-built
copies of the packages (the `nns-deps:link` workflow), so they carried
.turbo logs, build/*.d.ts and .tsbuildinfo that the published tarballs do
not ship, and their build/index.js hunk was diffed against a CJS bundle
while 7.10.0 publishes ESM. They could never apply to a registry install.

The only load-bearing change in ~200KB of patch noise was one feature:
rendering the `caption` property on bookmark blocks, which upstream
7.10.0 does not do (it renders title and description only). Without it,
32 authored captions silently vanished from nigeria-agriculture and
nigeria-agriculture-research. There is no `Bookmark` entry in
NotionComponents, so no supported override hook exists — hence a patch.

- notion-types patch: deleted. That package's build/index.js is 33 bytes
  of sourcemap comment and it is imported for types only, so the patch
  could not affect runtime.
- react-notion-x patch: regenerated against the pristine tarball. Now 23
  lines touching one file, versus 200KB+ of build artifacts.

Verified with a from-scratch `npm install` in a clean directory: exits 0
and the patch applies. Rendering is unchanged except that captions on
link-only bookmarks now show too — the previous fork dropped those, and
nothing is lost relative to it.

Also adds .nvmrc: Netlify was building on Node 18.20.8 against an
engines field requiring >=22.9.0 (npm warned EBADENGINE), while GitHub
Actions uses 22.9.0. Pin both to the same version.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-08-06 05:53 UTC

…resh

next build and next export each flagged three config problems on every
run:

- assetPrefix was "" when no base path is set; next requires >=1 char.
  Pass undefined instead.
- images.path was ""; same validator. Set "/". Verified all 74 rendered
  image URLs are byte-identical before and after.
- swcMinify sat under `experimental`, where next 12 ignores it and warns.
  Moved to top level, which means it now actually applies — rendered text
  is unchanged on all 10 pages; only JS chunk hashes differ.

Also documents the deploy setup that was previously tribal knowledge:

- Netlify's four load-bearing UI settings, each with the failure it
  prevents. None live in this repo, so a fresh Netlify site wired to it
  hits all four in sequence.
- Publishing is a two-step process — writing in Notion or Substack does
  nothing until `npm run fetch-content` is re-run and data/ committed.
- Notion signs file-attachment URLs with a short expiry, so links to
  uploaded files go stale hours after a refresh. Images are unaffected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Korede-TA
Korede-TA merged commit 5be294b into main Aug 6, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant