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-plugin-vue-middleware

v0.2.0

Published

File-based navigation guards for Vue Router

Readme

vite-plugin-vue-middleware

npm version NPM Downloads build status license

File-based navigation guards for Vue Router with full type safety and automated middleware injection.

✨ Features

  • 🚀 Zero-Config: Automatically scans your middleware directory and generates configurations.
  • 🛡️ Type-Safe: Autogenerates .d.ts files to provide full IntelliSense for vue-router's RouteMeta.
  • 📦 Virtual Module: Seamlessly integrate with your router using virtual:vue-middleware.
  • 🔄 HMR Support: Adding, removing, or renaming middleware files triggers hot updates and type regeneration.
  • 🛠️ Flexible Order: Supports global middleware (.global) and custom execution weight via numeric prefixes.
  • 🔗 Async Context: Automatically preserves Vue injection context (inject()) across await boundaries in async middleware via a build-time transform.

📦 Installation

Using npm:

npm install -D vite-plugin-vue-middleware

Using yarn:

yarn add -D vite-plugin-vue-middleware

Using pnpm:

pnpm add -D vite-plugin-vue-middleware

🚀 Quick Start

1. Configure the Plugin

Add the plugin to your vite.config.ts:

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import vueMiddleware from 'vite-plugin-vue-middleware';

export default defineConfig({
  plugins: [
    vue(),
    vueMiddleware({
      // Optional: Custom middleware directory (default: 'src/middleware')
      middlewareDir: 'src/middleware',
      // Optional: Custom d.ts generation path (default: 'middleware.d.ts')
      dts: 'src/types/middleware.d.ts',
    }),
  ],
});

2. TypeScript Setup (Required)

To ensure TypeScript recognizes the virtual module and the generated types, follow these steps:

A. Add to env.d.ts

Reference the plugin's client types in your global declaration file:

/// <reference types="vite-plugin-vue-middleware/client" />

Alternatively, add it to compilerOptions.types in your tsconfig.json:

{
  "compilerOptions": {
    "types": ["vite-plugin-vue-middleware/client"]
  }
}

B. Include the generated .d.ts

Ensure your tsconfig.json includes the generated type file. If you use the default path (project root), you must add it to the include array:

{
  "include": [
    "src/**/*",
    "src/**/*.vue",
    "./middleware.d.ts" // Required if generated at project root (default)
  ]
}

Note: if you generate the file inside src/ (e.g., src/types/middleware.d.ts), it will likely be covered by your existing "src/**/*" rule.

3. Create Middleware

Create middleware files in your src/middleware directory:

// src/middleware/01.auth.global.ts
import { defineMiddleware } from 'virtual:vue-middleware';

export default defineMiddleware((to, from) => {
  const isLogged = false; // simulate auth state
  if (!isLogged && to.path !== '/login') {
    return '/login';
  }
});

4. Inject into Router

Import and use setupMiddleware during your router initialization:

// src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router';
import { setupMiddleware } from 'virtual:vue-middleware';

const router = createRouter({
  history: createWebHistory(),
  routes: [
    /* your routes */
  ],
});

// Automatically bind middleware logic to router guards
setupMiddleware(router);

export default router;

🛠 How it Works

The plugin provides a wrapper around router.beforeEach to handle the middleware lifecycle:

  1. Global Middlewares: Executed first, followed by the order of their numeric prefixes (e.g., 01.log.ts before 02.auth.ts).
  2. Named Middlewares: Executed next, based on the order defined in the route's meta.middleware array.

Return Values

Handlers support async/await and follow the same logic as vue-router guards:

  • return: Continue to the next middleware or navigation.
  • return false: Abort the navigation.
  • return '/path': Redirect to a specific path.
  • return { name: 'login' }: Redirect to a named route.

📏 Naming Conventions

The plugin uses file naming to determine middleware properties:

| Rule | Example | Description | | :------------------- | :--------------- | :---------------------------------------------------------- | | Global Execution | auth.global.ts | Applied to all route navigations automatically. | | Execution Order | 01.log.ts | Numeric prefix determines weight (lower numbers run first). | | Named Middleware | guest.ts | Manually referenced in route meta or SFC definePage. |

Using Named Middleware in Pages

const routes = [
  {
    path: '/dashboard',
    component: () => import('./Dashboard.vue'),
    meta: {
      // Benefit from generated .d.ts with full IntelliSense
      middleware: ['auth', '02-analytics'],
    },
  },
];

🌟 Integration with Vue Router v5 (File-based Routing)

This plugin is fully compatible with the modern Vue Router v5 ecosystem and unplugin-vue-router (which provides the official File-based Routing logic for Vue). You can define middleware directly in your .vue files using the definePage macro:

<script setup lang="ts">
/**
 * Using definePage (Vue Router v5 / unplugin-vue-router)
 */
import { definePage } from 'unplugin-vue-router/runtime';

definePage({
  meta: {
    // You'll get the same type-safe IntelliSense here!
    middleware: ['auth'],
  },
});
</script>

⚙️ Configuration

| Option | Type | Default | Description | | :-------------- | :------------------ | :----------------- | :------------------------------------------------------------------------------------------------ | | middlewareDir | string | 'src/middleware' | Root directory to scan for middleware. | | exclude | string[] | [] | Glob patterns to ignore files. | | dts | boolean \| string | true | Enable/Disable .d.ts generation or specify path. | | asyncContext | boolean | true | Preserve Vue injection context (inject()) across await boundaries via a build-time transform. |


🔗 Async Context

By default, asyncContext is enabled. It applies a build-time AST transform to every async middleware function that contains await, allowing inject() and composables that rely on it (e.g. useQueryClient(), useStore()) to work correctly even after await statements.

The Problem

Vue's inject() API only works inside an active component or application context. In standard async middleware, calling inject() after an await boundary fails because the execution context is lost:

// ❌ inject() called after await — throws outside of Vue context
export default defineMiddleware(async (to, from) => {
  await someAsyncCheck();
  const store = useStore(); // Runtime error: inject() must be called inside setup()
});

How It Works

When asyncContext: true (default), the plugin transforms async middleware into a generator-based executor at build time. Each segment between yield points runs inside app.runWithContext(), restoring the Vue injection context on every resume:

// ✅ Works — inject() is available on every segment after await
export default defineMiddleware(async (to, from) => {
  await someAsyncCheck();
  const store = useStore(); // Works correctly
});

The transform is transparent — you write standard async/await and the plugin handles the rest. No manual changes are required.

Limitations

  • for await...of: Not supported with the async-context transform. A warning is logged and the affected middleware runs without context preservation across await boundaries.
  • Nested async functions: Only top-level await expressions in the defineMiddleware callback are transformed. Nested async functions are left untouched.

To opt out of the transform entirely:

vueMiddleware({ asyncContext: false });

📄 License

MIT License © 2026 Roya