vite-spa-seo

Ultra-lightweight Build-Time Prerendering & Runtime SEO Meta Injector for Vite SPAs (React, Vue, Svelte). Zero-config Puppeteer snapshots + zero-overhead Express middleware.
201
Install
npm install vite-spa-seo
Latest Version:2.1.0
License:MIT
Last Updated:Oct 23, 2026
Links: GitHub  ·  npm
Maintainer: dev-6392

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.

Related Packages

vite-ssr

Vite utility for server side rendering

64,502 833
@mcn-info/vite-ssr

Vite utility for server side rendering

2,023
express-vite-plugin

Zero-config Vite SSR integration plugin for Express and NestJS applications.

5
@meadmin/midway-vite-view

midway 的 vite 服务端渲染和客户端渲染组件,

3,844 10
@arponascension/express-inertia

Inertia.js adapter and middleware for Express.js — build SPAs with Vue 3, React,...

1,620 2

Version History

Version Released License
2.1.0 MIT
1.1.0 MIT
1.0.0 MIT
Pre-releases (1)
Version Released License
0.0.0-stage