@flashyos/page
v0.1.0
Published
One page, one card, one claim — per-page social images, canonical metadata and structured data for a property that expects to be cited by search engines and answered from by generative ones.
Maintainers
Readme
@flashyos/page
One page, one card, one claim. Per-page social images, canonical metadata and structured data, composed from the page's own facts in the same call that sets its title.
Apache-2.0. Peer-depends on next and react; brings nothing else.
The measurement it exists for
node tools/estate-pages.mjs across thirteen properties, 2026-08-31:
| | | | |---|---|---| | An image of its own | 79 / 733 | 11% | | …or an inherited one | 539 / 733 | 74% | | A title of its own | 564 / 733 | 77% | | A description | 457 / 733 | 62% | | A canonical URL | 212 / 733 | 29% | | Structured data | 145 / 733 | 20% |
The shape of that is a set of properties that are good at metadata and bad at identity. Nearly every page has a title. Barely any page has a card of its own, one address, or a machine-readable claim about what it is — and 539 pages render an ancestor's card, the same rectangle on every sibling.
Usage
Declare the property once:
// src/lib/page.ts
import { pageMetadata, pageJsonLd, type Property } from '@flashyos/page';
export const SITE: Property = {
origin: 'https://flashyos.com',
name: 'FlashyOS',
domain: 'flashyos.com',
accent: 'mint', // the kit's assignment table — one per property
};
export const meta = (page) => pageMetadata(SITE, page);Then each page states its own facts:
export const metadata = meta({
path: '/directory',
title: 'The Directory — verifiable relationship claims',
description: 'A provenance envelope for relationship claims…',
eyebrow: 'Directory',
});That one call sets the title, the description, the canonical, the Open Graph and Twitter cards, and the card's own URL — so a page's card cannot disagree with its title, because the same call writes both.
The card route
One route, not a file per segment. Next resolves opengraph-image.tsx per
segment and walks up, which means 733 cards is 733 files — and is why the
estate had 79.
// src/app/og/route.tsx
import { ImageResponse } from 'next/og';
import { CARD_SIZE, card } from '@flashyos/page';
export const runtime = 'edge';
export function GET(req: Request) {
const q = new URL(req.url).searchParams;
return new ImageResponse(
card({ eyebrow: q.get('e') ?? '', title: q.get('t') ?? '', accent: 'mint', domain: 'flashyos.com' }),
CARD_SIZE,
);
}Clip the inputs. A public renderer that draws arbitrary text is a renderer somebody will put something rude in; what it must not become is unbounded.
What it will not do
It will not invent a description. No falling back to the site's, no deriving one from the title. A page with nothing of its own to say should say so in review rather than ship a card repeating the homepage — which is the defect being fixed, 539 times over.
It will not date a page it cannot date. dateModified is omitted rather
than stamped with the build time, because a summariser repeats that date as
fact.
It will not restate the organisation. publisher is an @id reference to
the org defined once at its own address. A page carrying the org's name, logo
and details is a page that disagrees with the charter the week it changes.
SEO and GEO are the same fields read twice
A search engine ranks a page. A generative engine decides whether to quote it and whether it can attribute the quote — and everything making the second possible is already on the first one's list. A canonical, so a citation resolves to one address. A description stating the claim rather than selling it. Structured data naming the entity and the organisation answering for it. A card, so the citation renders as something.
One function, so a property cannot do the search half and skip the other.
The mark
BOLT_PATH and ACCENT restate the canonical kit — the one restatement here,
because Satori renders inside a build with no access to a property's vendored
brand/. tools/brand-drift.test.mjs in flashyos compares both against
flashy-group and fails on disagreement.
It found the first drift on its first run: apps/marketing/src/app/opengraph-image.tsx
drew a bolt of its own, on the social card of the property that publishes the
standard.
