Guide
Search and sharing
facet takes care of how a page looks; search engines read what is in the page and its
<head>. This page lists what every facet site should add, and what
facet already does for you.
What facet already does
- Content lives in the HTML. Story, panels and deck pages read fine without JavaScript, so a crawler indexes what a visitor sees.
-
Every layout uses semantic elements (
main,nav,article,section) and a heading hierarchy. -
Fonts are self-hosted with
font-display: swap, so text never waits for a font, and the Chinese heading font is sliced so a page downloads only the glyphs it uses. - Text contrast is at least 4.5:1, motion respects reduced-motion settings, and every control works from the keyboard. Accessibility and search reward the same things.
-
The cats are decoration: they carry
aria-hiddenand never mix into the page's text.
The head of every page
Give every page its own title and description. Put the page first and the site name after,
within about 60 characters; write the description, 50 to 160 characters, for a reader
scanning search results. The canonical link, the share image and
og:url are absolute URLs.
<html lang="en" data-accent="moss"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>Chasing the red dot · Duna's notes</title> <meta name="description" content="Twelve rooms, one red dot, and what Duna learned about light." /> <link rel="canonical" href="https://example.com/notes/red-dot/" /> <meta property="og:type" content="article" /> <meta property="og:title" content="Chasing the red dot" /> <meta property="og:description" content="Twelve rooms, one red dot, and what Duna learned about light." /> <meta property="og:url" content="https://example.com/notes/red-dot/" /> <meta property="og:image" content="https://example.com/notes/red-dot/card.png" /> <meta name="twitter:card" content="summary_large_image" /> <link rel="icon" href="/favicon.svg" type="image/svg+xml" /> <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#13161c" /> <meta name="theme-color" media="(prefers-color-scheme: light)" content="#f5f7f9" /> <link rel="preload" href="/facet/dist/fonts/geist.woff2" as="font" type="font/woff2" crossorigin /> <link rel="stylesheet" href="/facet/dist/facet.css" /> </head>
- Share cards are 1200×630 PNG or JPEG, ideally under 300 KB, with large text, since previews on phones are small.
-
The two
theme-colorvalues are facet's--f-bgin dark and light, so the browser's address bar matches the page. - Preload only the one or two fonts the first screen uses, usually Geist for text and Source Serif 4 for headings. Preloading more slows the first screen down.
-
When the fonts come from another origin, such as jsDelivr, the preload needs
crossorigin, or the browser downloads the font twice.
Structured data for each layout
A block of JSON-LD in the <head> tells search engines what a page is.
Each layout usually maps to one schema.org type:
| Layout | Type | Notes |
|---|---|---|
| Reading, Margin notes | BlogPosting |
headline, datePublished, image, and an author
that points at the site's Person.
|
| Docs | TechArticle |
Plus a BreadcrumbList for its place in the docs.
|
| Resume | ProfilePage + Person |
sameAs lists your profiles elsewhere, tying them to this site.
|
| List | CollectionPage |
An ItemList can name each entry's URL.
|
| Split | SoftwareSourceCode |
For a project page: codeRepository, license,
version.
|
| Cover, Story, Panels | VideoObject |
When the page has a video: name, thumbnailUrl,
uploadDate, contentUrl.
|
| Deck | PresentationDigitalDocument |
Optional; WebPage is enough for most decks.
|
| App | WebApplication |
applicationCategory, such as GameApplication.
|
| Empty | None | As a 404 page, the server must really answer 404. |
| Focus | ContactPage |
For a contact page; a link hub is a WebPage.
|
| Landing | ProfilePage |
For a personal home page; the Person is its mainEntity.
|
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BlogPosting",
"headline": "Chasing the red dot",
"datePublished": "2026-10-11T09:00:00-07:00",
"image": "https://example.com/notes/red-dot/card.png",
"author": { "@id": "https://example.com/#person" }
}
</script>
Refer to one Person by its @id instead of repeating it: the home
page defines it, and every other page names only the @id.
Notes on the layouts
-
Cover: keep the title and intro as HTML over the image, never baked into it. Don't
lazy-load the hero image; give it
fetchpriority="high"instead. -
Deck:
data-f-slideholds only positions and colors; every word belongs in the slide's markup. Crawlers don't read JSON in attributes. -
Deck, panels and app pages still need one
h1when the design shows no big title;f-visually-hiddenkeeps it out of sight. -
Docs and margin notes: give headings stable
ids so search results can link straight to a section. Sidenotes are already in the DOM. -
Images: meaningful
alttext for content,alt=""for decoration;widthandheightso the page doesn't shift as they load;loading="lazy"below the first screen. -
App: a tool with little text gets a short description in the HTML; signed-in or private
states get
noindex.
Bilingual sites
Search engines index only the HTML a URL serves, so a language switch that swaps text in
the browser leaves the other language unindexed. Give each language its own URL, mark it
with lang, and link the two with hreflang:
<html lang="zh-Hans"> <link rel="canonical" href="https://example.com/zh/notes/red-dot/" /> <link rel="alternate" hreflang="en" href="https://example.com/notes/red-dot/" /> <link rel="alternate" hreflang="zh-Hans" href="https://example.com/zh/notes/red-dot/" /> <link rel="alternate" hreflang="x-default" href="https://example.com/notes/red-dot/" />
-
Both language versions list every
hreflang, including themselves, and each page's canonical points at itself, never at the other language. -
facet's Chinese rules follow
lang:<html lang="zh-Hans">gives the whole page Chinese leading and line breaking, and a block withlang="en"inside it goes back to the Latin leading.
Sitemaps and robots.txt
-
sitemap.xmllists every canonical URL and nothing else: no redirects, no duplicate first pages of a paginated list, nothing markednoindex. -
Crawlers read
robots.txtonly at the domain root. A GitHub Pages project site (such asexample.com/project/) can't have its own, so add aSitemap:line for it to the root site's robots.txt, or submit it in Search Console. -
Drafts, mockups and test pages get
<meta name="robots" content="noindex">and stay out of the sitemap.
How this site does it
Each reference page declares only its <title> and
<meta name="description">, each with a Chinese data-zh.
When just site assembles the site, scripts/site_meta.py writes
every page again under zh/ with the Chinese in the HTML, and the toolbar's EN
/ 中文 moves between the two URLs (under just serve it still swaps in place).
It then adds the rest from the title and description: the canonical link,
hreflang alternates, icon, theme-color, Open Graph tags and
JSON-LD (WebSite and SoftwareSourceCode on the home page, a
WebPage with breadcrumbs elsewhere), writes sitemap.xml, and
marks the mockups noindex. scripts/card.py draws the share card
from facet's own tokens and fonts.