Every page GeekPresent builds is prerendered to static HTML, so it can
carry real search-engine and social-share metadata baked right into the file —
no JavaScript required for a crawler to read it. This page is a Text artifact like any other, and it is
itself the demo: scroll to what this page emits to see
its own <head> tags, pulled live from the document.
The shells (SlideDeck for presentations, TextPage for
texts) already render a reusable Seo component, so every slide and
every text emits, with no extra work:
<title> and a meta description;og:title, og:description, og:type, og:url, og:image, og:site_name) for Facebook, LinkedIn, Slack, Discord…;summary_large_image with title,
description and image);<link rel="canonical">;In-page assets stay relative so the build is portable to any sub-path or
straight off disk. But a few metadata fields — og:url, og:image, twitter:image, canonical and
the sitemap — must be absolute, because scrapers and search engines
can’t resolve a relative URL. One build-time variable supplies that base:
# default: the project's GitHub Pages URL
pnpm build
# a custom domain
GEEKPRESENT_SITE_URL=https://my.site pnpm build
# unknown deploy URL? omit the absolute-only tags
# (no half-formed og:url ever ships)
GEEKPRESENT_SITE_URL= pnpm build This build used https://nawaman.github.io/GeekPresent. When it is empty, the absolute-only
tags are simply left out — everything else (title, description, card type)
still ships.
A presentation sets its default description (and optional social image) once, in
its +layout.svelte, by passing props to SlideDeck:
<SlideDeck
{pages}
title="My Talk"
description="What this deck is about, in a sentence or two."
image="my-talk/social.png" <!-- optional; site-relative or absolute -->
width={1920} height={1080}
>
<slot />
</SlideDeck> A Text does the same through <TextPage> — which is
exactly what this page’s own +layout.svelte does:
<TextPage
title="SEO & Social Cards — GeekPresent"
description="How GeekPresent gives every page real SEO metadata…"
>
<slot />
</TextPage> Any single slide can override the deck defaults from its pages.ts entry — the same place it already sets its title and favicon:
export const pages = [
{ path: "title.html", title: "Title" },
{
path: "highlight.html",
title: "The Big Idea",
description: "A custom description just for this slide.",
image: "slides/highlight-og.png"
},
]; The cascade is: slide value → deck default → site default. Images fall back to the bundled 1200×630 default card.
Here are the actual SEO tags in this page’s <head> right
now, read live from the document — the same bytes a crawler sees in the
prerendered HTML:
Reading the document head… (this box needs JavaScript; the tags
are in the prerendered HTML regardless — view source to confirm). Notice the og:url and canonical are absolute and point
at this route, while the page’s images and assets stayed relative.
That split is the whole trick.
A full build also writes sitemap.xml (every
prerendered route, as absolute URLs) and robots.txt into the output. The sitemap is generated from each presentation’s pages.ts plus the standalone texts, so new slides are picked up
automatically. (A single-route build — build-static.sh ./out
seo.html — omits the sitemap by design.)
That’s the whole feature. Back to the home page, or read the sample Text next door.