SEO & social cards

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.

What you get, automatically

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:

  • a <title> and a meta description;
  • OpenGraph tags (og:title, og:description, og:type, og:url, og:image, og:site_name) for Facebook, LinkedIn, Slack, Discord…;
  • Twitter / X cards (summary_large_image with title, description and image);
  • a <link rel="canonical">;
  • a site-wide sitemap.xml and robots.txt in the build output.

The one knob: the base URL

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.

Per-deck defaults

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>

Per-slide overrides

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.

What this page emits

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.

Sitemap & robots

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.