Skip to content

OllieJT/schemeta

Repository files navigation

sche/ma in a pixelated monospaced font, on a blue-green glowing background

Schemeta

Schemeta streamlines & simplifies the creation of web metadata,
making it easier to leverage standards such as OpenGraph, and JSON-LD.

NPM Version Downloads GitHub Stars

Why does this exist?

You might be thinking, why does this exist? You're not crazy. Metadata is simple, repeatable, and boring. It's exactly this kind of boilerplate that I want to avoid in my work, and the kind of task that is easily overlooked while prioritising more unique tasks.

Schemeta was created to codify the building of metadata for a page, or site. Much like how frameworks impose a concise and repeatable structure to rabidly scaffold the common needs of projects - Schemeta provides a universal type-safe solution for making the most out of tags.

Finally, there are many metadata specifications, and values that are overlooked or skipped; like JSON-LD, or Apple Web-App tags. With type-safe methods, I hope it will be easier for consumers of this package to disover new ways to express their data to support their users, or promote their content.

Features

  • 🥫 Open Source
  • đź’  Framework Agnostic
  • âś… Works in server & on the client
  • 🙅‍♂️ No runtime dependensies
  • 🟦 Typescript Support
  • ⛓️ Convenient optional-chaining API

Supported Metadata

Note

Are we missing any? file an issue to let us know.

Source Type Details
OpenGraph RDFa The Open Graph protocol
SchemaOrg JSON-LD Schema.Org
Microsoft Metadata Windows Pinned Sites
Twitter Metadata Twitter Cards
Apple Metadata Safari MetaTags + Apple Web Apps
Pinterest Metadata developers.pinterest.com

Overview

Define Data

At it's core, schemeta exposes a class that can be used to define the metadata for a page. Schemeta will automatically handle overriding values that are only expected once (like the page title) or appending values that can appear several times (like images).

import { Metadata } from "schemeta";

// Create an instance of Metadata
const metadata = new Metadata();

// Apply your data
metadata
	.title("Ollie's Profile")
	.description("Get to know the developer behind this project.")
	.type({
		type: "profile",
		params: { first_name: "Ollie", last_name: "Taylor", username: "OllieJT" },
	});

// Easily append values
if (page.profile.photo) {
	metadata.image({ src: page.profile.photo, alt: page.profile.photo_alt });
}
if (page.profile.avatar) {
	metadata.image({ src: page.profile.avatar, alt: page.profile.avatar_alt });
}

Render meta-tags on the server (optional)

The metadata can be defined on the server before transforming the data into a useable format.

The way you render meta-tags will likely vary based on your framework. We'll use a simple vanilla-js example, but you can find more examples below.

Server

import { Metadata } from "schemeta";

const metadata = new Metadata()
	.title("My Server-Rendered App")
	.url("https://github.com/olliejt/schemeta");

// Option 1: Send HTML code to the client
const html = metadata.toString();

// Option 2: Send values to initialize Metadata on the client
const values = metadata.toValues();

res.send({ html, values });

Option 1: Render HTML on the client

import { Metadata } from "schemeta";

// Recieve values from the server
const { html } = get_server_data();

/**
 * This example inserts HTML meta tags before the closing </head> tag.
 *
 * @param {string} html - The HTML string containing meta tags.
 */
function render_meta_tags(html) {
	const parser = new DOMParser();
	const doc = parser.parseFromString(html, "text/html");
	const tags = doc.head.childNodes;

	tags.forEach((tag) => {
		document.head.appendChild(tag);
	});
}

render_meta_tags(metadata_html);

Option 2: Initialize Metadata on the client

import { Metadata } from "schemeta";

// Recieve values from the server
const { values } = get_server_data();

// Option 2: Initialize wih metadata from the server
const metadata = new Metadata(values).title("My Client-Rendered App");

// Render your html with one of the outputs (.toString() | .toHTML() | .toElements())
render_meta_tags(metadata.toString());

Setup

Installation

# npm
npm install schemeta@latest

# pnpm
pnpm install schemeta@latest

# yarn
yarn add schemeta@latest

Usually you will use schemeta on each page. However, there are some meta-tags you will want to define globally for all pages.

Here is an example I would use in a global layout template.

import { Metadata } from "schemeta";

const metadata = new Metadata();

metadata
	.add({ key: "og:site_name", value: "DesignThen" })
	.title("The tech stack behind our projects in 2024")
	.description(
		"Curious about DesignThen's approach? Gain insights into our design & development philosophy, and the tools shaping our work.",
	)
	.type({
		type: "article",
		params: {
			authors: ["Ollie Taylor"],
			published_time: new Date("2024-02-03T16:00:00Z"),
			section: "Web Development",
		},
	})
	.add({
		key: "application/ld+json",
		value: {
			"@type": "Article",
			author: { "@type": "Person", name: "Ollie Taylor" },
			headline: "The tech stack behind our projects in 2024",
			datePublished: "2024-02-03T16:00:00Z",
		},
	});

API

Metadata Instance

new Metadata(initial?: MetadataInitialValues)

Add values

method Purpose
core: .add(key, value) Add individual supported values like OpenGraph, or JSON-LD
helper: .title(string) Adds several title values like og:title & twitter:title
helper: .description(string) Adds several description values like og:description & twitter:description
helper: .url(string) Adds several page url values like og:url & canonical
helper: .image(params) Adds one image with optional properties like og:alt & og:width
helper: .site_name(string) Adds several site name values like og:site_name & apple-mobile-web-app-title
helper: .type(type, params) Adds og:type and optional related OpenGraph values

Note

Got an idea for a common helper method? file an issue to let us know, or better yet submit a PR!

Render elements

method output usecase
.toValues() MetadataValues Values needed to initialize Metadata.
.toElements() { element: string; attributes: Record<string, string>; children?: string; }[] Enables you to manually render html elements.
.toHTML() string[] Allows you to render each html element.
.toString() string Allows you to render all html elements.

Undocumented

Most helper functions and types are exposed, however we won't be documenting them here until the library has matured more and implementation details have been validated.

Examples

Vanilla Example

TODO

SvelteKit Example

SSR Metadata for every route (Optional)

If you want to consistently pass metadata from server to client, you can enforce this with PageData types in app.d.ts

// /src/app.d.ts
declare global {
	namespace App {
		interface PageData {
			metadata: import("schemeta").MetadataValues;
		}
	}
}

Render Metadata Tags

You can create a component to handle the rendering of your metadata elements.

<!-- RenderMetadata.svelte -->
<script lang="ts">
	import type { ValueElement } from "schemeta";

	// Svelte 5 Syntax
	const { elements = [] }: { elements: ValueElement[] } = $props();

	// Svelte 3 + 4 Syntax
	export let elements: ValueElement[] = []
</script>

<svelte:head>
	{#each elements as { element, attributes, children }}
		{#if children}
			<svelte:element this={element} {...attributes}>
				{String(children)}
			</svelte:element>
		{:else}
			<svelte:element this={element} {...attributes} />
		{/if}
	{/each}
</svelte:head>
<!-- /src/routes/+page.svelte -->
<script lang="ts">
	import { Metadata } from "schemeta";
	import RenderMetadata from "$components/RenderMetadata.svelte";

	let { data } = $props();

	const metadata = new Metadata(data.metadata);
</script>

<RenderMetadata elements={metadata.toElements()} />

React Example

TODO