@bigin-io/renderer-core
v1.0.1
Published
The framework-agnostic half of both renderers: theme derivation, section chrome, SEO, i18n routes, the image-variant planner, media transform URLs, markdown, icons, and the form/analytics/search mounts
Readme
@bigin-io/renderer-core
The rendering half of the renderer. @bigin-io/renderer-nuxt is the binding
half — the framework-facing layer over this package plus
@bigin-io/site-contract (ADR-1, ADR-13, ADR-13a).
Story 7.0a withdrew the second renderer. @bigin-io/renderer-next was the
other binding half and its last release is 0.7.0, which stays installable;
site.config's renderer field now has one legal value. This package keeps its
boundary for two reasons that do not depend on a second renderer:
@bigin-io/site-tools consumes it, and the rule ADR-13a actually states is that
a module belongs here when it neither imports nor structurally describes a
framework — which is a property of the module, not a headcount of consumers.
Extracted from renderer-nuxt in story 1.11: 58 of its 70 modules and 5,461
of its 7,223 lines. The twelve that stayed are almost entirely @nuxt/kit
wiring — which is why this package is named for what it is rather than as a
toolkit.
The rule for what belongs here
Is this rendering, or is this framework binding?
That question decides the package, and it is the primary test. Import-purity is
necessary and not sufficient: setup.ts imports nothing framework-shaped and
is nevertheless the Nuxt module's body, described structurally instead of
imported. Module-level is not sufficient either: a module can be rendering and
still carry one export that is binding.
A question cannot run in CI, so two guards enforce it — see
test/boundary.test.ts:
- Structural, both ways. No module here may name a framework token
(
@nuxt/,addTemplate,#site-factory/,.vue,:locale, …). And every module inrenderer-nuxt/srcmust name one, or sit on a two-entry exemption table with a written reason — because a module that could have moved and did not is a boundary decided by inertia. - Literal. No module here may contain
/_nuxt,/_next,__nuxt,__next,.vueor a:localeroute segment.
When the guards and the question disagree, the question wins and the guard
widens. That has already happened once: the literal rule's first draft flagged
og:locale in seo/meta.ts, which is an Open Graph property and plainly
rendering. The fix was a tighter pattern, not an exemption — exempting the file
would have taught the guard the wrong thing and blinded it to a real :locale
next door.
Entry points
. is paths; the rest mirror the areas: /analytics, /config, /content,
/forms, /i18n, /icons, /images, /markdown, /media, /migration,
/pages, /search, /sections, /seo, /theme.
/node is separate on purpose
sharp is Node-only, and the other entries are reachable from a browser graph
through the section components. A value import that dragged it in would ship a
native image encoder to a visitor — the same failure as 4.5a's API_PREFIX,
where one value import cost 83.9 KB raw of Zod and the Turnstile verifier in a
browser bundle, one package boundary down.
So the filesystem, sharp and the build-time emitters live behind
@bigin-io/renderer-core/node, and a test walks the real transitive import
graph of every other entry to prove the package does not appear. It is not left
to a bundler's tree-shaking.
satori was the other one, and it is gone (story 7.0b). It was an optional
peer, deferred behind a dynamic import, because canGenerateOgImages() required
a heading face whose format is not woff2 and all three design presets declare
woff2 only — so no site could ever draw an OG card. The generator is withdrawn
rather than fixed. sharp stays and is a real dependency: ADR-12's build-time
image pipeline applies to both tiers and never had anything to do with cards.
What a renderer must supply
Three things this package deliberately refuses to know, because each one names a framework and each fails differently when shared:
| Supplied by the renderer | Used by | What happens if the kit held it |
| --- | --- | --- |
| RendererIdentity (renderer, packageName, siblingPackageName) | loadSiteConfig | Every Next build rejects a correct renderer: 'next' config and tells the author to install Nuxt |
| The internal-path list (/_nuxt/, __nuxt_content) | isPageRoute | Next's build assets reach sitemap.xml and get OG cards drawn for them |
| The build-asset globs (/_nuxt/*) | renderHeadersFile, headerRules | A Next site caches a directory it does not have and never caches /_next/static/*, which is content-hashed — invisibly, forever, with nothing failing |
The third is the one worth remembering: both sites serve correct bytes, so no test, gate or parity check catches it. It was found by a guard, not by review.
Fonts and icons
The bundled families and the Lucide collection are dependencies here, not of
a renderer, because bundling them is rendering: both renderers must ship the
identical bytes for parity (NFR-7, story 1.10). A Next site gets Google Sans
Flex, Fraunces, IBM Plex Mono and Lucide without asking, with the vietnamese
subset included and no third-party request on any page.
