Schemeta streamlines & simplifies the creation of web metadata,
making it easier to leverage standards such as OpenGraph, and JSON-LD.
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.
- 🥫 Open Source
- đź’ Framework Agnostic
- âś… Works in server & on the client
- 🙅‍♂️ No runtime dependensies
- 🟦 Typescript Support
- ⛓️ Convenient optional-chaining API
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 |
Metadata | Twitter Cards | |
Apple | Metadata | Safari MetaTags + Apple Web Apps |
Metadata | developers.pinterest.com |
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 });
}
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.
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 });
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);
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());
# 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",
},
});
new Metadata(initial?: MetadataInitialValues)
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!
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. |
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.
TODO
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()} />
TODO