Add initial project files

This commit is contained in:
Igor Martyniuk
2026-09-01 13:03:41 +03:00
parent 22e111bb44
commit 74cb5b6176
1841 changed files with 242819 additions and 30 deletions
+71
View File
@@ -0,0 +1,71 @@
# Web Manager — Public Site (Next.js)
How the public site renders what [Web Manager](./Readme.md) publishes. Source: `apps/web/web-runtime/`, `apps/web/app/`.
## Structure
```txt
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`, plus `x-default` pointing 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_image` with an image, `summary` without; `og:type` is `article` for
`blog-item`, `website` otherwise.
`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.