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

vite-spa-seo

v2.1.0

Published

Ultra-lightweight Build-Time Prerendering & Runtime SEO Meta Injector for Vite SPAs (React, Vue, Svelte). Zero-config Puppeteer snapshots + zero-overhead Express middleware.

Readme

vite-spa-seo

npm version License: MIT Dependencies RAM Overhead Node.js CI

High-performance, hybrid SEO and social metadata injection engine for Single Page Applications (Vite, React, Vue, Svelte) on Node.js / Express.

Delivers complete server-side metadata, OpenGraph cards, Twitter cards, Google Shopping Schema.org JSON-LD, and pre-rendered semantic HTML in ~2 milliseconds — eliminating runtime Chromium dependencies and server memory overhead without requiring a full rewrite to Next.js or Nuxt.


Overview

Single Page Applications (SPAs) built with modern bundlers like Vite output a minimal index.html file containing an empty root mounting element:

<div id="root"></div> <!-- or <div id="app"></div> -->

While modern browsers execute JavaScript to render the full user interface, major web crawlers (Googlebot, Bingbot), search engine indexers, and social media scrapers (WhatsApp, Facebook, Twitter, LinkedIn, Slack) frequently evaluate only the initial server-delivered HTML payload. As a result, SPAs often suffer from:

  • Incomplete Search Indexation: Missing unique page titles, meta descriptions, and product schema.
  • Blank Social Share Previews: Inability to display rich thumbnail cards on chat and social platforms.
  • Server Resource Exhaustion: Traditional headless browser renderers (Puppeteer, Rendertron) consume 500 MB–1 GB+ of RAM per instance, causing out-of-memory (OOM) crashes on standard cloud VPS instances.

vite-spa-seo resolves this through a two-tier hybrid architecture:

  1. Build-Time Prerendering (CLI): Generates static, fully rendered HTML snapshots of primary routes during the build process on your local or CI machine.
  2. Zero-Overhead Runtime Middleware (Express): Automatically serves pre-rendered snapshots when available, and dynamically injects metadata and structured JSON-LD for thousands of database-driven dynamic routes in under 2ms.

Key Features

  • Zero Runtime Dependencies: Core middleware is written in pure Node.js with no external dependencies.
  • Zero RAM Footprint on Production: No Chromium or browser instances running on your live web server.
  • Automatic Framework & Container Detection: Seamlessly supports React (#root), Vue (#app), Svelte, and custom root containers.
  • Smart Directory Resolution: Automatically discovers dist, build, or custom frontend paths without mandatory manual configuration.
  • Rich Snippet Ready: Built-in helpers for Google Shopping, BreadcrumbList, Articles, and Schema.org JSON-LD.
  • Sub-2ms In-Memory Caching: High-throughput memory cache with configurable TTL and instantaneous programmatic invalidation.
  • Seamless Client Hydration: React and Vue mount smoothly over injected content without hydration mismatch warnings.

Installation

# In your backend project (Express / Node.js)
npm install vite-spa-seo

# Optional: In your frontend project for build-time static snapshots
npm install -D vite-spa-seo puppeteer

Quick Start

Basic Implementation in Express (server.js)

Mount the middleware in your Express application before your static file handlers:

const express = require('express');
const { createSpaSeoMiddleware } = require('vite-spa-seo');

const app = express();

app.use(
  createSpaSeoMiddleware({
    siteUrl: 'https://example.com',
    siteName: 'Example Store',

    // Static Routes
    staticPages: {
      '/': {
        title: 'Home | Example Store',
        description: 'Explore our catalog of premium curated products with fast shipping.',
      },
      '/about-us': {
        title: 'About Us | Example Store',
        description: 'Learn more about our mission, journey, and our global team.',
      },
    },

    // Dynamic Parameterized Routes
    routes: [
      {
        path: '/product/:sku',
        handler: async ({ params }) => {
          const product = await database.findProductBySku(params.sku);
          if (!product) return null; // Fallback to standard client-side 404 handling

          return {
            title: `${product.name} | Example Store`,
            description: product.description,
            image: product.imageUrl,
            schema: {
              '@type': 'Product',
              name: product.name,
              sku: product.sku,
              offers: {
                price: product.price,
                priceCurrency: 'USD',
                availability: product.inStock
                  ? 'https://schema.org/InStock'
                  : 'https://schema.org/OutOfStock',
              },
            },
          };
        },
      },
    ],
  })
);

app.listen(3000, () => {
  console.log('Application running on port 3000');
});

Build-Time Prerendering (Optional)

To produce static HTML snapshots containing the complete, rendered DOM for static pages and high-priority dynamic routes, execute the prerender CLI during build time.

1. Configure Build Script in package.json

{
  "scripts": {
    "build": "vite build && vite-spa-seo prerender --routes /,/about-us,/contact,/pricing"
  }
}

2. Configuration File (vite-spa-seo.config.js)

For larger projects, create a configuration file in the project root:

module.exports = {
  distDir: './dist',
  routes: [
    '/',
    '/about-us',
    '/contact',
    '/privacy-policy',
    '/terms-conditions',
    '/product/featured-item-1',
    '/product/featured-item-2',
  ],
  readySelector: '#root > *, #app > *', // Awaits framework mounting
  concurrency: 2,
};

When deployed to production, the Express middleware automatically detects any pre-rendered files and serves them directly with zero injection latency.


Configuration Reference

createSpaSeoMiddleware(options)

| Option | Type | Default | Description | | :--- | :--- | :--- | :--- | | distDir | string | (Auto-detected) | Path to the directory containing index.html. Searches ./dist, ../dist, ./build, ../frontend/dist. | | indexHtmlPath | string | distDir/index.html | Explicit path to the HTML template file. | | siteUrl | string | '' | Base canonical domain (e.g., https://example.com). | | siteName | string | '' | Brand name populated in og:site_name. | | rootSelector | string | <div id="root"></div> | Target container for semantic fallback content. Auto-matches #root or #app. | | cacheTTL | number | 3600000 (1h) | Duration in milliseconds to retain rendered HTML strings in memory. Set 0 to disable. | | routes | Array<DynamicRoute> | [] | Array of dynamic route definitions and asynchronous handlers. | | staticPages | StaticPagesMap | {} | Key-value mapping of exact paths to static metadata objects or provider functions. | | ignorePaths | Array<string \| RegExp> | ['/api', '/admin'] | Request prefixes or patterns excluded from SEO handling. | | serveAssets | boolean | true | Automatically serves files from dist/assets with immutable caching headers. |


Route Handler Return Object (SeoData)

interface SeoData {
  title?: string;
  description?: string;
  canonical?: string;
  image?: string;
  type?: string;                     // e.g., 'website', 'product', 'article'
  twitterCard?: 'summary' | 'summary_large_image';
  openGraph?: Record<string, string | number | boolean>;
  schema?: Record<string, any> | Array<Record<string, any>>;
  breadcrumbs?: Array<{ name: string; url: string }>;
  bodyContent?: string;              // Custom semantic HTML injected inside the root element
}

Examples

Dynamic E-Commerce Product Route

{
  path: '/product/:sku',
  cacheTTL: 30 * 60 * 1000, // 30-minute cache per product
  handler: async ({ params }) => {
    const item = await catalog.getBySku(params.sku);
    if (!item) return null;

    return {
      title: `${item.title} | Store`,
      description: item.summary,
      canonical: `/product/${item.sku}`,
      image: item.thumbnailUrl,
      schema: {
        '@context': 'https://schema.org',
        '@type': 'Product',
        name: item.title,
        sku: item.sku,
        image: [item.thumbnailUrl],
        offers: {
          '@type': 'Offer',
          price: item.price,
          priceCurrency: 'USD',
          availability: item.quantity > 0
            ? 'https://schema.org/InStock'
            : 'https://schema.org/OutOfStock',
        },
      },
      breadcrumbs: [
        { name: 'Home', url: '/' },
        { name: item.categoryName, url: `/category/${item.categorySlug}` },
        { name: item.title, url: `/product/${item.sku}` },
      ],
    };
  },
}

Blog / Editorial Article

{
  path: '/articles/:slug',
  handler: async ({ params }) => {
    const post = await posts.findBySlug(params.slug);
    if (!post) return null;

    return {
      title: `${post.headline} | News`,
      description: post.excerpt,
      image: post.coverImage,
      type: 'article',
      openGraph: {
        'article:published_time': post.publishedDate,
        'article:author': post.author,
      },
    };
  },
}

Cache Management

The middleware exposes programmatic cache invalidation through the .purgeCache() method:

const seoMiddleware = createSpaSeoMiddleware({ ... });
app.use(seoMiddleware);

// Invalidate specific route when a record is updated
app.post('/api/webhook/product-updated', (req, res) => {
  const { sku } = req.body;
  seoMiddleware.purgeCache(`/product/${sku}`);
  res.json({ success: true, purged: `/product/${sku}` });
});

// Purge all stored HTML representations
app.post('/api/admin/clear-cache', (req, res) => {
  seoMiddleware.purgeCache();
  res.json({ success: true, message: 'All SEO caches cleared' });
});

Performance & Benchmarks

| Metric | Traditional SSR / Puppeteer | vite-spa-seo | Improvement | | :--- | :--- | :--- | :--- | | Server RAM Usage | 500 MB – 1.2 GB | < 15 MB | 98% reduction | | Response Latency | 800ms – 2,500ms | ~2ms | 99% faster | | Server Crash Risk (OOM) | High on low-tier VPS | Zero | Enterprise-grade stability | | Setup Time | Days (Full refactoring) | < 5 minutes | Instant integration |


Testing

Execute the comprehensive unit and integration test suite:

npm test

License

Distributed under the MIT License. See LICENSE for details.

Developed and maintained by Devendra.