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 🙏

© 2026 – Pkg Stats / Ryan Hefner

tanstack-sitemap

v1.1.6

Published

A sitemap builder written in TypeScript for Tanstack start

Readme

🗺️ TanStack Sitemap

A powerful, type-safe sitemap builder for TanStack Start (Router) applications

npm versionLicense: MITTypeScript

✨ Features

  • 🎯 Type-Safe: Full TypeScript support with intelligent route parameter extraction
  • 🚀 TanStack Integration: Built specifically for TanStack Start (Router) applications
  • 🔧 Vite Plugin: Seamless integration with Vite build process
  • 🌐 Dynamic Routes: Support for parameterized routes with custom data providers
  • 📱 Rich Media: Support for images, videos, and news in sitemaps
  • 🌍 Internationalization: Built-in support for alternate language links
  • 🎨 Flexible: Runtime generation or build-time plugin usage

📦 Installation

# npm
npm install tanstack-sitemap

# yarn
yarn add tanstack-sitemap

# pnpm
pnpm add tanstack-sitemap

🚀 Quick Start

Vite Plugin

Add the plugin to your vite.config.ts:

import { defineConfig } from 'vite'
import { SitemapConfig, sitemapPlugin } from "tanstack-sitemap";
import type { FileRouteTypes } from "./src/routeTree.gen";


type Routes = FileRouteTypes["fullPaths"];

const sitemap : SitemapConfig<Routes> = {
      defaults: {
        baseUrl: "https://your-site.com",
        changeFrequency: "weekly",
        priority: 0.7,
      },
      routes: {
        "/": { priority: 1.0, changeFrequency: "daily" },
        "/about": { priority: 0.8 },
        "/blog/$slug": {
          provideParams: async () => {
            // Fetch your blog posts
            const posts = await getBlogPosts();
            return posts.map((post) => ({ slug: post.slug }));
          },
          priority: 0.6,
          changeFrequency: "monthly",
        },
      },
    }

export default defineConfig({
  plugins: [
    sitemapPlugin(sitemap)
  ]
})

Runtime Generation

Create a server route file named sitemap[.]xml.ts and use the generateSitemap function in it:

import { createServerFileRoute } from "@tanstack/react-start/server";
import type { FileRouteTypes } from "../routeTree.gen";
import { generateSitemap } from "tanstack-sitemap";

type Routes = FileRouteTypes["fullPaths"];

export const ServerRoute = createServerFileRoute("/sitemap.xml").methods({
  GET: async () => {

     // You can separately create the sitemap object and pass it to generateSitemap.
    const sitemapXml = await generateSitemap<Routes>({
      defaults: {
        baseUrl: "https://your-site.com",
        changeFrequency: "weekly",
        priority: 0.7,
      },
      routes: {
        "/": { priority: 1.0, changeFrequency: "daily" },
        "/about": { priority: 0.8 },
        "/blog/$slug": {
          provideParams: async () => {
            // Fetch your blog posts
            const posts = await getBlogPosts();
            return posts.map((post) => ({ slug: post.slug }));
          },
          priority: 0.6,
          changeFrequency: "monthly",
        },
      },
    });

    return new Response(sitemapXml, {
      headers: {
        "Content-Type": "text/xml",
      },
    });
  },
});

📚 Configuration

Basic Configuration

interface SitemapConfig<TRoutes extends string> {
  defaults: {
    baseUrl: string
    changeFrequency?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never'
    priority?: number
    lastModified?: Date | string
  }
  routes?: {
    [K in TRoutes]?: RouteConfig<K>
  }
}

Static Routes

{
  routes: {
    '/': {
      priority: 1.0,
      changeFrequency: 'daily',
      lastModified: new Date(),
      alternatives: [
        { hrefLang: 'en', href: 'https://example.com/en' },
        { hrefLang: 'es', href: 'https://example.com/es' }
      ]
    },
    '/about': {
      priority: 0.8,
      changeFrequency: 'monthly'
    }
  }
}

Dynamic Routes

Dynamic routes use the $parameter syntax and require a provideParams function:

{
  routes: {
    '/blog/$slug': {
      provideParams: async () => {
        const posts = await fetch('/api/posts').then(r => r.json())
        return posts.map(post => ({ slug: post.slug }))
      },
      priority: 0.6,
      changeFrequency: 'weekly',
      // Dynamic content based on parameters
      dynamicImages: async ({ params, path }) => [
        {
          loc: `/images/blog/${params.slug}.jpg`,
          caption: `Image for ${params.slug}`,
          title: `Blog post: ${params.slug}`
        }
      ]
    },
    '/products/$category/$id': {
      provideParams: async () => {
        const products = await getProducts()
        return products.map(p => ({
          category: p.category,
          id: p.id
        }))
      }
    }
  }
}

🖼️ Rich Media Support

Images

{
  routes: {
    '/gallery': {
      images: [
        {
          loc: '/images/hero.jpg',
          caption: 'Hero image',
          geoLocation: 'New York, NY',
          title: 'Beautiful sunset',
          license: 'https://creativecommons.org/licenses/by/4.0/'
        }
      ]
    }
  }
}

Videos

{
  routes: {
    '/videos': {
      videos: [
        {
          thumbnailLoc: '/thumbnails/video1.jpg',
          title: 'Amazing Video',
          description: 'An amazing video description',
          contentLoc: '/videos/amazing.mp4',
          duration: 120,
          rating: 4.5,
          viewCount: 1000,
          familyFriendly: true,
          tags: ['amazing', 'video', 'content']
        }
      ]
    }
  }
}

News

{
  routes: {
    '/news/$slug': {
      provideParams: async () => getNewsArticles(),
      dynamicNews: async ({ params }) => [
        {
          publicationName: 'Daily News',
          publicationLanguage: 'en',
          title: `News: ${params.slug}`,
          publicationDate: new Date(),
          keywords: 'news, breaking, update'
        }
      ]
    }
  }
}

🌍 Internationalization

{
  routes: {
    '/$lang': {
      provideParams: async () => [{ lang : "en" } , { lang : "es" } , { lang : "fr" }]
      alternatives: [
        { hrefLang: 'en', href: '/en' },
        { hrefLang: 'es', href: '/es' },
        { hrefLang: 'fr', href: '/fr' },
        { hrefLang: 'x-default', href: '/en' }
      ]
    }
  }
}

🏗️ Advanced Usage

Multiple Dynamic Parameters

{
  routes: {
    '/blog/$year/$month/$slug': {
      provideParams: async () => {
        const posts = await getBlogPosts()
        return posts.map(post => ({
          year: post.publishedAt.getFullYear().toString(),
          month: (post.publishedAt.getMonth() + 1).toString().padStart(2, '0'),
          slug: post.slug
        }))
      }
    }
  }
}

Conditional Route Generation

{
  routes: {
    '/products/$id': {
      provideParams: async () => {
        const products = await getProducts()
        // Only include published products
        return products
          .filter(product => product.status === 'published')
          .map(product => ({ id: product.id }))
      }
    }
  }
}

Custom Last Modified Dates

{
  routes: {
    '/blog/$slug': {
      provideParams: async () => getBlogPosts(),
      // Set lastModified per route
      lastModified: async ({ params }) => {
        const post = await getBlogPost(params.slug)
        return post.updatedAt
      }
    }
  }
}

🔧 API Reference

generateSitemap(config: SitemapConfig)

Generates a sitemap XML string based on the provided configuration.

Returns: Promise<string> - The generated sitemap XML

sitemapPlugin(config: SitemapConfig)

Vite plugin that generates a sitemap during the build process.

Returns: Plugin - Vite plugin instance

Type Definitions

// Route parameter extraction (automatic)
type ExtractParams<'/blog/$slug'> = { slug: string }
type ExtractParams<'/shop/$category/$id'> = { category: string; id: string }

// Route configuration types
interface BaseSitemapEntry {
  changeFrequency?: ChangeFrequency
  priority?: number
  lastModified?: Date | string
  alternatives?: AlternateLink[]
  images?: SitemapImage[]
  videos?: SitemapVideo[]
  news?: SitemapNews[]
}

interface DynamicRouteConfig<T extends Record<string, string>> extends BaseSitemapEntry {
  provideParams: () => Array<T> | Promise<Array<T>>
  dynamicAlternatives?: (context: { params: T; path: string }) => AlternateLink[] | Promise<AlternateLink[]>
  dynamicImages?: (context: { params: T; path: string }) => SitemapImage[] | Promise<SitemapImage[]>
  dynamicVideos?: (context: { params: T; path: string }) => SitemapVideo[] | Promise<SitemapVideo[]>
  dynamicNews?: (context: { params: T; path: string }) => SitemapNews[] | Promise<SitemapNews[]>
}

📋 Examples

E-commerce Store

sitemapPlugin({
  defaults: {
    baseUrl: 'https://shop.example.com',
    changeFrequency: 'weekly',
    priority: 0.5,
  },
  routes: {
    '/': { priority: 1.0, changeFrequency: 'daily' },
    '/products': { priority: 0.9 },
    '/products/$category': {
      provideParams: async () => {
        const categories = await getCategories()
        return categories.map(cat => ({ category: cat.slug }))
      },
      priority: 0.8
    },
    '/products/$category/$slug': {
      provideParams: async () => {
        const products = await getProducts()
        return products.map(p => ({
          category: p.category.slug,
          slug: p.slug
        }))
      },
      priority: 0.7,
      dynamicImages: async ({ params }) => {
        const product = await getProduct(params.slug)
        return product.images.map(img => ({
          loc: img.url,
          caption: img.alt,
          title: product.name
        }))
      }
    }
  }
})

Blog with Categories

sitemapPlugin({
  defaults: {
    baseUrl: 'https://blog.example.com',
    changeFrequency: 'weekly',
  },
  routes: {
    '/': { priority: 1.0, changeFrequency: 'daily' },
    '/blog': { priority: 0.9 },
    '/blog/category/$category': {
      provideParams: async () => {
        const categories = await getCategories()
        return categories.map(cat => ({ category: cat.slug }))
      },
      priority: 0.8
    },
    '/blog/$slug': {
      provideParams: async () => {
        const posts = await getPosts()
        return posts.map(post => ({ slug: post.slug }))
      },
      priority: 0.7,
      changeFrequency: 'monthly',
      dynamicAlternatives: async ({ params }) => {
        const post = await getPost(params.slug)
        return post.translations.map(t => ({
          hrefLang: t.language,
          href: `https://blog.example.com/${t.language}/blog/${t.slug}`
        }))
      }
    }
  }
})

Development Setup

# Clone the repository
git clone https://github.com/your-username/tanstack-sitemap.git
cd tanstack-sitemap

# Install dependencies
pnpm install

# Run tests
pnpm test

# Build the package
pnpm build

# Run formating
pnpm format

# Run linting
pnpm lint

📄 License

MIT © Amir Akbarpour

🙏 Acknowledgments

📞 Support