How content moves from Directus or the fixture into typed modules, and the shared contracts that keep the exporter and the site in agreement.
| Reference | Value |
|---|---|
| Subject | Exporter, source policy, generated modules, shared schemas, locales, asset contracts; for anyone changing content shapes or the build export |
| Verified source | export-fallbacks.ts, apps/cms/scripts/lib, packages/shared on main at f40e8704, checked 2026-11-07; build-published images (build-media.ts, #677), project one-liner validation (#678) and image block fields (#691) re-verified on main at 0461e045, checked 2026-11-10 |
| Support state | Export and metric-output contracts checked against main f40e8704; the registry in lib/export-modules.ts declares 21 outputs. Site and llms-full.txt metric output excludes source/date fields |
flowchart LR
D["Directus<br/>published rows"] -- "fetchers" --> EX["export-fallbacks.ts"]
FX["packages/shared/fixtures<br/>export-data.json"] --> EX
C[".cms-cache.json<br/>local only"] -. "soft fallback" .-> EX
EX -- "validate with<br/>@repo/shared schemas" --> EM["emitter registry"]
EM --> MOD["apps/web/src/lib/content<br/>21 modules"]
MAN["assets-manifest.json<br/>assets-id-map.json"] --> EX
EX --> MV["media variants"]
D -- "Build Bot read<br/>unmirrored images" --> BM["static/images/cms<br/>generated, ignored"]
EX --> BM
Saving CMS content does not itself start a build. A production build uses live CMS content, whether triggered by Publish site or a merge to main. A merge after a CMS content write therefore publishes that content when the production build succeeds; the code diff does not bound the content published.
| Target | Detected by | Source | On failure |
|---|---|---|---|
| Vercel Production | VERCEL_ENV=production |
Live CMS only; fixture mode is refused | Build fails before emitting |
| Vercel Preview | Any other VERCEL_ENV |
Fixture; a live request is ignored with a warning |
n/a |
| Local and CI | No VERCEL_ENV |
Fixture by default; EXPORT_FALLBACKS_SOURCE=live for an explicit operator read |
Live failure falls back to .cms-cache.json; no cache means failure |
The whole live fetch has a 60 second stall guard. Exit codes come from decideExit: under the production fail policy anything but a live export fails, and every policy fails when no source exists.
lib/validators/live-export.ts).block_* singletons must be published, or the live build fails.site_pages section disappears from routes, OG cards, LLM text and the sitemap on the next build. The /, /services, /projects and /blog entries are required, and the last three must have listing type: archiving one fails export, so the build fails and the active production deployment keeps serving.fixtures/assets-manifest.json must exist and match its SHA-256 digest.https://cms.yesid.dev with the build token, checks the decoded image (PNG, JPEG, WebP, GIF or AVIF; 20 MiB; 16,000 px per axis; 40 million pixels; one frame) and, after all content validates, writes it with responsive WebP variants to apps/web/static/images/cms/<file-uuid>/<sha256>.<ext>. A new SVG, an unreadable file or an image block with neither a CMS file nor a site-local path fails export. Fixture exports map such references to a labelled placeholder without network (lib/build-media.ts, lib/content-media.ts).apps/web/.cache/cms-media/, keyed by source hash and re-checked before reuse; originals are read on every live export.layout (row or beside) and showBothThemes; the shared schema rejects other layout values (packages/shared/src/types/blocks.ts).one_liner is normalized to NFC and must render in full on the project OG card at 18px or larger with the bundled fonts, or export fails naming the project, locale and field (web/src/lib/og/validate-project-lines.ts).lib/export-modules.ts is the single registry for fetches, schemas, cross-entity checks and outputs. emitters/configs.ts derives output configurations from it. Fetchers in lib/fetchers/ map Directus rows to product types.