Publishing Notion Content
How Notion content gets from the CMS into production.
Mental model
Source of truth for deploys = git commits, not live Notion calls.
| Artifact | In git? | How it is updated |
|---|---|---|
public/search-index.json | Yes | npm run publish:content (local) |
public/.notion-cache/ | Yes | npm run publish:content (local) |
public/components/ | Yes (local/next dev cache) | npm run publish:content (local) |
| Component seeds (Export/Preview) | No β Netlify Blobs | publish:content β components:seeds:upload |
public/_redirects, netlify/redirect-data.json | No | Regenerated on every npm run build (Netlify prebuild) |
Netlify never persists .notion-cache between builds. Every deploy uses whatever is in the repo at that commit. Runtime component TSX is not served from git/out/ β publish:content uploads new seeds to Blobs so Export works after deploy.
Full pipeline
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β LOCAL PUBLISH (when Notion content changes) β
β β
β npm run publish:content β
β 1. build:search β
β 2. build:metadata (incremental, no --force) β
β 3. build:search (re-index after metadata) β
β 4. build:content --refresh-shell + homepage gallery merge β
β 5. build:artifacts (metadata + components) β
β 6. validate:artifacts β
β 7. components:seeds:upload (new Blobs seeds; skip exists) β
β β
β git add -f public/search-index.json public/.notion-cache/ β
β public/components/ β
β git commit -m "chore: publish notion content" β
β git push origin main β
ββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β NETLIFY DEPLOY (automatic on every push) β
β β
β prebuild β redirects, edge-data, mcp-discovery, skills β
β next build β static export to /out (strips components/code) β
β Export/Preview reads Blobs via Components API β
β ~3 min, zero Notion API calls β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
One command to publish
npm run publish:content
git add -f public/search-index.json public/.notion-cache/ public/components/
git commit -m "chore: publish notion content"
git push origin main
Requires .env.local with NOTION_API_KEY, NOTION_TOKEN, and GEMINI_API_KEY, plus Netlify CLI auth for seed upload (netlify login + netlify link, or NETLIFY_AUTH_TOKEN + NETLIFY_SITE_ID).
Netlify deploys automatically. No cache clear needed. New pattern seeds are already in Blobs from step 7 β no separate upload.
Secondary commands (low-level)
Use these only when you need a targeted run. Normal publishing should use publish:content.
| Command | Purpose |
|---|---|
npm run build:search | Rebuild public/search-index.json only |
npm run build:content | Incremental Notion page cache |
npm run build:content -- --refresh-shell | Refresh homepage + menuGroup DB views |
npm run build:content -- --force | Overwrite all .notion-cache files |
npm run build:metadata | AI descriptions + Notion sync (incremental) |
npm run build:components | Component TSX exports (incremental) |
npm run build:artifacts | Metadata + components orchestrator |
npm run validate:artifacts | Write artifacts report only |
npm run components:seeds:upload | Ship local TSX seeds to Netlify Blobs (also step 7 of publish:content) |
Force flags (--force) are for recovery or full rebuilds β not part of the default publish flow.
Escape hatch: PATTERN_PUBLISH_SKIP_SEEDS_UPLOAD=1 npm run publish:content skips Blobs upload (debug only β new patterns will not Export on deploy).
--refresh-shell explained
build:content skips existing cache files by default. New pattern pages get their own cache file, but homepage and category database views can stay stale.
publish:content always passes --refresh-shell, which re-fetches:
- Homepage (
siteConfig.notionPageId) - Menu group DBs:
ux-patterns,ui-patterns,all-patterns
It also merges the patterns gallery collection_query from the all-patterns shell into the homepage cache (Notionβs homepage fetch omits that data).
Env var reference (content builds)
| Var | Effect | Default |
|---|---|---|
NOTION_DEEP_COVER_SCAN | Absent = content-body image scan on | On |
NOTION_COVER_DEBUG | Verbose cover/image logs | Off |
NOTION_FORCE_IMAGE_SCAN | Re-scan Notion body images (ignore cached images[]) | Off |
NOTION_API_DEBUG | Verbose Notion request logs | Off |
NOTION_FAIL_FAST | Fail on first Notion error | Follows CI env |
GEMINI_API_KEY | Required for metadata/components generation | β |