B Blengi docs

Operate

Marketing reference pages

Marketing reference pages

The nav and footer of the marketing site promise a set of pages beyond the seven the theme registry owns: the /solutions family, /compare, the /integrations children, and the company and trust pages, the one comparison page and the demo. There are sixteen of them and they share one shape, so they are data rather than fourteen controllers and fourteen components.

Where a page lives

  • app/Support/MarketingLandingPages.php — the URL map. One entry per page: its meta title and description, the eyebrow shown above the heading, and an optional parent used to build the breadcrumb.
  • app/Support/MarketingLandingCopy.php — the body copy, split out purely so the URL map stays readable.
  • app/Http/Controllers/MarketingLandingController.php — one action for all of them.
  • The theme component for the landing page key. Harvest renders pages/marketing/landing.tsx; a theme that declares landing in its theme.json renders its own.

Section types

A page is a heading, a one-line lede, and a list of typed sections. Adding a page is a data edit. Adding a section type is a change in every theme's landing component, so prefer composing from what exists.

  • prose — a title and a list of paragraphs.
  • cards — a title, an optional description, and items of {title, description}.
  • bullets — a title, an optional description, and plain strings.
  • faq — items of {question, answer}. A page carrying one automatically emits FAQPage structured data, the same builder the home page uses.
  • cta — a title, an optional description, and one button. With site_test_placeholder and site_test_button_label filled, the theme renders the website-address capture instead of the button; the address travels to /demo?url=…, where the conversation starts on arrival.
  • ribbon — one line pointing at a live customer install, with an optional link (text, link_label, link_href).
  • try_now — the demo itself: the capture, the live conversation with the visitor's own site, then the signup leg (placeholder, button_label, signup_label). Only the /demo page carries one.

A page may also carry hero_button_label and hero_button_href for one action under the lede, and hero_secondary_label / hero_secondary_href for a quieter second one. A theme that does not draw a section type skips it; it never renders a blank or a white screen.

A registry entry may set noindex => true. The page is then served with <meta name="robots" content="noindex,nofollow"> and left out of the sitemap — indexablePaths() is what the sitemap reads — from that one flag. No page ships with it on: /blog is the handoff's blog index with three announced articles, and the file carries a normal canonical, so it is indexed like every other page. Flipping the flag is the operator's call.

The eyebrow of a page under a section reads Section · Page ("Solutions · Ecommerce", "Integrations · Notion"), as the handoff prints it; the breadcrumb still takes the parent's own eyebrow for the middle crumb.

Routing

All fourteen are served by a single wildcard route registered inside the localised marketing group, so each one gets its /nl/… twin, its .l route name, and the marketing-site kill switch without any per-page work.

The route's where constraint is a regex alternation of the exact registered paths, built by routePattern(). This matters: an unconstrained /{path} would happily answer /pricing and /privacy depending on registration order. With the constraint, an unregistered URL is a router 404 and the rest of the marketing site is untouchable. A test asserts exactly that.

The footer's Developers column points at /docs, which is a permanent redirect to /documentation rather than a second copy of this site.

Things that are wired for you

  • The sitemap. SeoController derives the entries from MarketingLandingPages::paths(), so a page added to the registry cannot be forgotten there.
  • Structured data. Every page emits BreadcrumbList from its parent chain, and FAQPage when it has an FAQ section.
  • Translation. Every string is collected by MarketingCopy, so it appears in the Translation Manager and can be localised without a deploy, exactly like the editable home and pricing copy.
  • White-labelling. Write {brand} in copy rather than a product name. The registry interpolates it before the text reaches the page, the meta tags or the structured data. A test fails if the token or the source product name ever reaches a rendered page.

The client handoff of 17 September 2026

The copy on the solutions, integrations, about and trust-center pages, plus /compare/blengi-vs-intercom and /demo, is the client's own per-page handoff (Design A, Clean SaaS). Each page carries exactly the sections the client's file has — the FAQ blocks that used to sit underneath were ours, not his, and were removed (#636). /blog ships as his index of three announced articles.

A corrected package arrived the same evening. It changed nothing but a relative path (assets/ was missing one ../, so every page opened unstyled from disk); the copy is identical, and with it rendering the pricing page's own design became visible and was applied in #633.

Several sentences in the handoff claimed things the product does not do. They were reworded to what is true, and tests/Feature/Marketing/HonestClaimsTest pins the rewording so a later paste cannot bring a claim back:

  • "Data stays on EU infrastructure, no US data-transfer question." Storage is on EU servers (Frankfurt). AI model calls run on Cloudflare's network with a US-based fallback provider, so the pages say exactly that.
  • "Median answer time under a second." Measured on live visitor traffic: 2.2 s median to the first words, about 6 s to a full answer. The stats say "2–3 s typical".
  • "RSS / Atom feeds." There is no feed source type. Replaced by the SQL database source, which exists.
  • "Stripe" as a knowledge integration. Stripe is the platform's billing gateway. The native list names Notion, Google Docs, Slack, WordPress & WooCommerce, Calendly and webhooks.
  • "Link a doc or a folder." Google Docs connect per document.
  • Calendly "on the roadmap". It shipped; it moved to the native list.
  • "Search for the plugin in the WordPress directory." The plugin is downloaded from the dashboard.
  • "Read the DPA" and "encrypted at rest". No DPA document exists yet and disk encryption is not something the application configures; the trust center offers a DPA on request and describes TLS plus encrypted secrets.
  • "Friendly upgrade prompt" when the quota is hit. New conversations are refused at init, so the widget does not start; the pricing FAQ says so.
  • The Stapp bot "answers stock questions". That shop is not on WooCommerce, so it answers from crawled pages: "sizing and product questions".

Two conflicts between the handoff and the plan rows were left to the operator, because the page reads the rows: the Managed setup fee (rows say €499, handoff says €999) and whether Growth includes white label (rows say yes, handoff says no).

Phone-only fields (#638)

The client's mobile-reference gives every page a short top-bar name and a hero pill, and splits some section headings into a small label plus a second line. Those live beside the desktop copy:

  • mobile_label and mobile_pill on a page entry in MarketingLandingPages ("Solutions · Support" / "Support"). Both fall back to the eyebrow.
  • mobile_eyebrow and mobile_title on a cards section. With a label and no second line the heading is hidden on phones, as the client's file prints it.
  • mobile_hidden on a faq item leaves it out of the phone accordion.

Adding a page

  1. Add an entry to MarketingLandingPages::all() with a unique meta title, a description, an eyebrow, and a parent when it sits under a section.
  2. Add its copy to MarketingLandingCopy.
  3. Link it from the nav or footer. A link with an empty or # href is dropped by the footer renderer, so an unlinked page is an invisible page.
  4. Run the suite. The route list, the sitemap and the private-install guard are all derived, so nothing else needs editing.