47 lines · 1.9 KB
import { OG_HEIGHT, OG_WIDTH, SITE_OG_ALT, siteOgPath } from "~/lib/og";

type SeoInput = {
  title: string;
  /** Tab title when it differs from the shared title (e.g. private repositories share only a generic one). */
  documentTitle?: string;
  description?: string;
  keywords?: string;
  /** Forge origin (PUBLIC_URL); relative `path` and `image` are resolved against it. */
  origin?: string;
  /** Canonical page path, e.g. `/owner/repo`. */
  path?: string;
  /** Card path from ~/lib/og; defaults to the site card. */
  image?: string;
  imageAlt?: string;
};

function absolute(origin: string | undefined, path: string): string {
  if (!origin) return path;
  return new URL(path, origin).toString();
}

export const seo = ({ title, documentTitle, description, keywords, origin, path, image, imageAlt }: SeoInput) => {
  const imageUrl = absolute(origin, image ?? siteOgPath());
  const alt = imageAlt ?? (image ? title : SITE_OG_ALT);
  const tags = [
    { title: documentTitle ?? title },
    { name: "description", content: description },
    { name: "keywords", content: keywords },
    { name: "twitter:card", content: "summary_large_image" },
    { name: "twitter:title", content: title },
    { name: "twitter:description", content: description },
    { name: "twitter:image", content: imageUrl },
    { name: "twitter:image:alt", content: alt },
    { property: "og:type", content: "website" },
    { property: "og:site_name", content: "gitfor" },
    { property: "og:title", content: title },
    { property: "og:description", content: description },
    ...(path ? [{ property: "og:url", content: absolute(origin, path) }] : []),
    { property: "og:image", content: imageUrl },