4.0 KiB
Web Manager — Public Site (Next.js)
How the public site renders what Web Manager publishes. Source: apps/web/web-runtime/, apps/web/app/.
Structure
app/[locale]/[[...path]]/page.tsx localized catch-all
app/[locale]/preview/page.tsx token-gated draft rendering, noindex, force-dynamic
app/api/revalidate/route.ts secret-guarded tag purge
app/sitemap.ts, app/robots.ts generated from the public API
web-runtime/api/ HTTP client, render-data resolution, cache tags
web-runtime/data/ transport normalization
web-runtime/blocks/ block renderers + registry
web-runtime/layouts/ page layouts and hero
web-runtime/chrome/ header and footer
web-runtime/components/ link, rich text and media primitives
web-runtime/lib/ metadata, site URL, sitemap, structured data
web-runtime/types.ts normalized public contract
web-runtime/renderer.tsx header + layout + footer
Locales come from site.config.ts (en, uk, de, default en); with more than one locale
next-intl always prefixes the URL.
Everything degrades rather than fails. An unknown page_type falls back to the standard layout; an
unknown block type renders a dev-only "renderer missing" banner and nothing at all in production;
the normalizer degrades malformed payloads instead of throwing. Publishing a block type the web app
does not know yet can therefore never take the site down.
Hero
Hero impact is an intensity choice, not a block type:
| Impact | Use for | Rendering |
|---|---|---|
none |
pages that start with content | hero omitted |
low |
legal, utility, index pages | compact padding, smallest title scale |
middle |
default marketing and content | medium padding, balanced scale |
high |
campaigns, launches, conversion | near-viewport height, largest scale, radial accent wash |
Media beside the hero switches it to a two-column grid at every impact. A page with no hero object falls back to a page-type eyebrow plus the title.
SEO
generateMetadata delegates to pageMetadata (web-runtime/lib/page-metadata.ts), a pure function
of the resolved render data:
- title / description — the SEO fields, falling back to the page title; an empty description is omitted rather than sent blank.
- canonical — the page's own locale-prefixed path.
- hreflang — one alternate per locale in
languageAlternatives, plusx-defaultpointing at the default locale, or at this page when the default locale has no version. Omitted for a page with no translations. - Open Graph / Twitter — the hero image becomes the share card with width, height and alt when
supplied;
summary_large_imagewith an image,summarywithout;og:typeisarticleforblog-item,websiteotherwise.
app/sitemap.ts walks the paginated sitemap endpoint (capped at 50 requests — the protocol's
50,000-URL limit) and hands entries to buildSitemap; pages sharing a groupKey link to each other
through alternates.languages, and lastModified uses updatedAt falling back to publishedAt.
app/robots.ts allows everything except /*/preview and advertises the sitemap.
web-runtime/lib/structured-data.tsx emits BreadcrumbList on every page with a trail and
Article on editorial pages — skipped entirely without a publication date rather than shipping
markup a crawler discards.
SITE_URL must be set. metadataBase comes from siteUrl(), and Next resolves every relative
metadata URL against it. Without it Next falls back to http://localhost:3000: the build succeeds
and pages look fine, but canonicals and share cards point at localhost. A production build without
it logs an error; a malformed value throws.