npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

tanstack-meta

v0.6.0

Published

A small library that helps you manage document heads type-safely in TanStack Router/Start

Readme

tanstack-meta

A small library that transforms structured, Next.js Metadata-like objects into metadata compatible with TanStack Router/Start, helping you manage document heads type-safely.

Why

TanStack Start is awesome, but there’s one thing I’m not fully satisfied with: its document head management, which I think still falls short of what Next.js offers. Simply assigning strings to name or content is straightforward and easy to understand, but it lacks sufficient autocompletion and makes it hard to catch mistakes, which does not offer a good DX. To improve this situation, I created a library that enables you to manage metadata as more structured objects and provides type-safe autocompletion.

Here's an example of how you can use tanstack-meta. While it makes the code slightly longer in this case, it provides a more organized structure and enables richer IDE type completion:

| Without tanstack-meta | With tanstack-meta | | ----------------------- | -------------------- | | Screenshot | Screenshot |

Usage

Install tanstack-meta:

npm install tanstack-meta@latest

Import generateMetadata from tanstack-meta and use it in your route's head function:

import { createFileRoute } from "@tanstack/react-router";
import { generateMetadata } from "tanstack-meta";

export const Route = createFileRoute("/")({
  component: Home,
  head: () => {
    const { meta, links } = generateMetadata({
      title: "TanStack Start App",
      description: "An example app built with TanStack Start.",
    });

    return { meta, links };
  },
});

You can use it almost the same way as Next.js's generateMetadata function.

Title Template

If you want to use a title template like Next.js's title.template, use createMetadataGenerator to create a customized metadata generator:

import { createMetadataGenerator } from "tanstack-meta";

// Create a generator with title template
const generateMetadata = createMetadataGenerator({
  titleTemplate: {
    default: "Default Title", // Used when title is not provided
    template: "%s | My Site", // %s is replaced with the page title
  },
});

// In your routes:
generateMetadata({ title: "About" });
// Output: <title>About | My Site</title>

generateMetadata({ title: null });
// Output: <title>Default Title</title>

generateMetadata({});
// Output: <title>Default Title</title>

You can also use a function for more complex title transformations:

const generateMetadata = createMetadataGenerator({
  titleTemplate: {
    default: "My Site",
    template: (title) => `${title.toUpperCase()} — My Site`,
  },
});

generateMetadata({ title: "about" });
// Output: <title>ABOUT — My Site</title>

// Conditional logic example
const generateMetadata = createMetadataGenerator({
  titleTemplate: {
    default: "My Site",
    template: (title) =>
      title.length > 50
        ? `${title.slice(0, 47)}... | My Site`
        : `${title} | My Site`,
  },
});

To opt out of the title template on a specific page, use title.absolute:

generateMetadata({ title: { absolute: "Home" } });
// Output: <title>Home</title> (template is ignored)

%s placeholders are all replaced. For example, template: "%s | %s | My Site" with title: "Docs" renders <title>Docs | Docs | My Site</title>.

Base URL

Similar to Next.js's metadataBase, you can use the baseUrl option to resolve relative URLs to absolute URLs for metadata fields like openGraph, twitter, and alternates:

import { createMetadataGenerator } from "tanstack-meta";

const generateMetadata = createMetadataGenerator({
  baseUrl: "https://example.com",
});

// Relative URLs are resolved to absolute URLs
generateMetadata({
  openGraph: {
    images: "/og.png",
  },
  alternates: {
    canonical: "/about",
  },
});
// Output:
// <meta property="og:image" content="https://example.com/og.png" />
// <link rel="canonical" href="https://example.com/about" />

You can also pass a URL object:

const generateMetadata = createMetadataGenerator({
  baseUrl: new URL("https://example.com"),
});

Absolute URLs are preserved unchanged:

generateMetadata({
  openGraph: {
    images: "https://cdn.example.com/og.png",
  },
});
// Output: <meta property="og:image" content="https://cdn.example.com/og.png" />

You can combine baseUrl with titleTemplate:

const generateMetadata = createMetadataGenerator({
  titleTemplate: { default: "My Site", template: "%s | My Site" },
  baseUrl: "https://example.com",
});

generateMetadata({
  title: "About",
  openGraph: {
    images: "/og.png",
  },
});
// Output:
// <title>About | My Site</title>
// <meta property="og:image" content="https://example.com/og.png" />

Reference

generateMetadata

Generates the document metadata compatible with TanStack Router/Start's head function.

Parameters

An object containing the document metadata to be set.

Return Value

An object containing meta and links properties, which can be used as the return value of the head function.

createMetadataGenerator

Creates a customized metadata generator with options like title templates and base URL resolution.

Parameters

An options object with the following properties:

  • titleTemplate (optional): An object containing:
    • default: The default title used when no title is provided
    • template: A template string where %s is replaced with the page title, or a function that receives the page title and returns the formatted title
  • baseUrl (optional): A string or URL object used to resolve relative URLs to absolute URLs. Applies to:
    • openGraph (images, audio, videos, url)
    • twitter (images, players, app URLs)
    • alternates (canonical, languages, media, types)

Return Value

A function that accepts metadata (with extended title support) and returns the same structure as generateMetadata.

Supported Metadata Fields

  • charSet
    • The character encoding of the document.
    • // Input
      { charSet: "utf-8" }
    • <!-- Output -->
      <meta charset="utf-8" />
  • title
    • The document title.
    • // Input
      { title: "My Blog" }
    • <!-- Output -->
      <title>My Blog</title>
    • When using createMetadataGenerator with a title template, you can also use { absolute: string } to bypass the template:
    • // Input (with createMetadataGenerator)
      { title: { absolute: "Special Page" } }
    • <!-- Output -->
      <title>Special Page</title>
  • description
    • The document description.
    • // Input
      { description: "My Blog Description" }
    • <!-- Output -->
      <meta name="description" content="My Blog Description" />
  • applicationName
    • The application name.
    • // Input
      { applicationName: "My Blog" }
    • <!-- Output -->
      <meta name="application-name" content="My Blog" />
  • authors
    • The authors of the document.
    • // Input
      { authors: [{ name: "TanStack Team", url: "https://tanstack.com" }] }
    • <!-- Output -->
      <meta name="author" content="TanStack Team" />
      <link rel="author" href="https://tanstack.com" />
  • generator
    • The generator used for the document.
    • // Input
      { generator: "TanStack Start" }
    • <!-- Output -->
      <meta name="generator" content="TanStack Start" />
  • referrer
    • The referrer setting for the document.
    • // Input
      { referrer: "origin" }
    • <!-- Output -->
      <meta name="referrer" content="origin" />
  • viewport
    • The viewport configuration for the document.
    • // Input
      {
        width: "device-width",
        initialScale: 1,
        themeColor: [
          { media: "(prefers-color-scheme: dark)", color: "#000000" },
          { media: "(prefers-color-scheme: light)", color: "#ffffff" }
        ],
        colorScheme: "dark"
      }
    • <!-- Output -->
      <meta name="viewport" content="width=device-width, initial-scale=1" />
      <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#000000" />
      <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff" />
      <meta name="color-scheme" content="dark" />
  • other
    • Arbitrary name/value pairs for additional metadata.
    • // Input
      { other: { custom: ["meta1", "meta2"] } }
    • <!-- Output -->
      <meta name="custom" content="meta1" />
      <meta name="custom" content="meta2" />
  • robots
    • The robots setting for the document.
    • // Input
      { robots: "index, follow" }
    • <!-- Output -->
      <meta name="robots" content="index, follow" />
    • // Input
      { robots: { index: true, follow: true } }
    • <!-- Output -->
      <meta name="robots" content="index, follow" />
  • keywords
    • The keywords for the document.
    • // Input
      { keywords: "tanstack, react, blog" }
    • <!-- Output -->
      <meta name="keywords" content="tanstack, react, blog" />
    • // Input
      { keywords: ["react", "tanstack query"] }
    • <!-- Output -->
      <meta name="keywords" content="react,tanstack query" />
  • pagination
    • The pagination link rel properties.
    • // Input
      {
        pagination: {
          previous: "https://example.com/items?page=1",
          next: "https://example.com/items?page=3"
        }
      }
    • <!-- Output -->
      <link rel="prev" href="https://example.com/items?page=1" />
      <link rel="next" href="https://example.com/items?page=3" />
  • openGraph
    • The Open Graph metadata for the document.
    • // Input
      {
        openGraph: {
          type: "website",
          url: "https://example.com",
          title: "My Website",
          description: "My Website Description",
          siteName: "My Website",
          images: [{ url: "https://example.com/og.png" }]
        }
      }
    • <!-- Output -->
      <meta property="og:title" content="My Website" />
      <meta property="og:description" content="My Website Description" />
      <meta property="og:url" content="https://example.com" />
      <meta property="og:site_name" content="My Website" />
      <meta property="og:image" content="https://example.com/og.png" />
      <meta property="og:type" content="website" />
  • twitter
    • The Twitter metadata for the document.
    • // Input
      {
        twitter: {
          card: "summary_large_image",
          site: "@site",
          creator: "@creator",
          images: "https://example.com/og.png"
        }
      }
    • <!-- Output -->
      <meta name="twitter:card" content="summary_large_image" />
      <meta name="twitter:site" content="@site" />
      <meta name="twitter:creator" content="@creator" />
      <meta name="twitter:image" content="https://example.com/og.png" />
  • facebook
    • The Facebook metadata for the document.
    • // Input
      { facebook: { appId: "12345678" } }
    • <!-- Output -->
      <meta property="fb:app_id" content="12345678" />
    • // Input
      { facebook: { admins: ["12345678"] } }
    • <!-- Output -->
      <meta property="fb:admins" content="12345678" />
  • pinterest
    • The Pinterest metadata for the document to choose whether opt out of rich pin data.
    • // Input
      { pinterest: { richPin: true } }
    • <!-- Output -->
      <meta name="pinterest-rich-pin" content="true" />
  • manifest
    • The web application manifest for the document.
    • // Input
      { manifest: "https://example.com/manifest.json" }
    • <!-- Output -->
      <link rel="manifest" href="https://example.com/manifest.json" />
  • icons
    • The icons for the document.
    • // Input
      { icons: "https://example.com/icon.png" }
    • <!-- Output -->
      <link rel="icon" href="https://example.com/icon.png" />
    • // Input
      {
        icons: {
          icon: "https://example.com/icon.png",
          apple: "https://example.com/apple-icon.png"
        }
      }
    • <!-- Output -->
      <link rel="icon" href="https://example.com/icon.png" />
      <link rel="apple-touch-icon" href="https://example.com/apple-icon.png" />
  • appleWebApp
    • The Apple web app metadata for the document.
    • // Input
      {
        appleWebApp: {
          capable: true,
          title: "My Website",
          statusBarStyle: "black-translucent"
        }
      }
    • <!-- Output -->
      <meta name="mobile-web-app-capable" content="yes" />
      <meta name="apple-mobile-web-app-title" content="My Website" />
      <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
  • appLinks
    • The Facebook AppLinks metadata for the document.
    • // Input
      {
        appLinks: {
          ios: { appStoreId: "123456789", url: "https://example.com" },
          android: { packageName: "com.example", url: "https://example.com" }
        }
      }
    • <!-- Output -->
      <meta property="al:ios:app_store_id" content="123456789" />
      <meta property="al:ios:url" content="https://example.com" />
      <meta property="al:android:package" content="com.example" />
      <meta property="al:android:url" content="https://example.com" />
  • itunes
    • The metadata for the iTunes App.
    • // Input
      {
        itunes: {
          app: {
            id: "123456789",
            affiliateData: "123456789",
            appArguments: "123456789"
          }
        }
      }
    • <!-- Output -->
      <meta name="apple-itunes-app" content="app-id=123456789, affiliate-data=123456789, app-arguments=123456789" />
  • alternates
    • The canonical and alternate URLs for the document.
    • // Input
      {
        alternates: {
          canonical: "https://example.com",
          languages: {
            "en-US": "https://example.com/en-US"
          }
        }
      }
    • <!-- Output -->
      <link rel="canonical" href="https://example.com" />
      <link rel="alternate" hreflang="en-US" href="https://example.com/en-US" />
  • verification
    • The common verification tokens for the document.
    • // Input
      { verification: { google: "1234567890", yandex: "1234567890", "me": "1234567890" } }
    • <!-- Output -->
      <meta name="google-site-verification" content="1234567890" />
      <meta name="yandex-verification" content="1234567890" />
      <meta name="me" content="1234567890" />
  • creator
    • The creator of the document.
    • // Input
      { creator: "TanStack Team" }
    • <!-- Output -->
      <meta name="creator" content="TanStack Team" />
  • publisher
    • The publisher of the document.
    • // Input
      { publisher: "TanStack" }
    • <!-- Output -->
      <meta name="publisher" content="TanStack" />
  • abstract
    • The brief description of the document.
    • // Input
      { abstract: "My Website Description" }
    • <!-- Output -->
      <meta name="abstract" content="My Website Description" />
  • archives
    • The archives link rel property.
    • // Input
      { archives: "https://example.com/archives" }
    • <!-- Output -->
      <link rel="archives" href="https://example.com/archives" />
  • assets
    • The assets link rel property.
    • // Input
      { assets: "https://example.com/assets" }
    • <!-- Output -->
      <link rel="assets" href="https://example.com/assets" />
  • bookmarks
    • The bookmarks link rel property.
    • // Input
      { bookmarks: "https://example.com/bookmarks" }
    • <!-- Output -->
      <link rel="bookmarks" href="https://example.com/bookmarks" />
  • category
    • The category meta name property.
    • // Input
      { category: "My Category" }
    • <!-- Output -->
      <meta name="category" content="My Category" />
  • classification
    • The classification meta name property.
    • // Input
      { classification: "My Classification" }
    • <!-- Output -->
      <meta name="classification" content="My Classification" />
  • formatDetection
    • Indicates whether devices should interpret certain formats (such as telephone numbers) as actionable links.
    • // Input
      { formatDetection: { telephone: false } }
    • <!-- Output -->
      <meta name="format-detection" content="telephone=no" />