news May 09, 2026 · 38 views · 3 min read

Streamline Vue Apps with Nuxt-Style Middleware in Vite

Discover a Vite plugin that seamlessly integrates Nuxt-style middleware into Vue apps. Enhance your Vue SPA with efficient navigation guards without leaving the Vite ecosystem.

Streamline Vue Apps with Nuxt-Style Middleware in Vite

Navigating through complex Vue apps can sometimes become cumbersome, especially when dealing with navigation guards. This becomes evident when your application requires more than basic authentication checks, such as analytics, permission management, feature flags, or prefetching. Traditionally, these logics pile up in the beforeEach hook, leading to cluttered and hard-to-maintain code.

Existing Solutions

Several community solutions attempt to address these challenges:

  • Custom Middleware Runners: Although functional, this approach involves reinventing the wheel for each project without type safety.
  • Vue-Router-Middleware-Plugin: While it is a close alternative, it lacks TypeScript support for middleware names and asynchronous context handling.
  • Nuxt: This full SSR framework is sometimes overkill for a simple SPA.

To bridge these gaps, a new solution emerges: vite-plugin-vue-middleware. This plugin aims to integrate middleware into Vue SPA as naturally as it does in Nuxt, all while staying within the Vite environment.

Key Challenges and Solutions

Problem 1: Overloaded beforeEach

Solution: Each middleware is defined in its own file within src/middleware/. The plugin scans this directory automatically, organizing and linking the middleware seamlessly.

src/middleware/
├── 01.auth.global.ts     ← runs on every route, first
├── 02.log.global.ts      ← runs on every route, second
├── admin.ts              ← only runs when the route opts in
└── guest.ts

Router setup transforms into a single line:

import { setupMiddleware } from 'virtual:vue-middleware'
setupMiddleware(router)

Problem 2: Lack of Type Safety for Middleware Names

Solution: The plugin generates a .d.ts file that enhances vue-router's RouteMeta, providing full type safety and IntelliSense for middleware names.

definePage({
  meta: {
    middleware: ['admin', 'guest'] // Autocomplete and error-checking
  },
})

Problem 3: inject() Failing Post-await

Vue's inject() fails in an async context after await, causing runtime errors. This is particularly problematic when using TanStack Vue Query as it relies on inject() internally.

Solution: The plugin employs a build-time AST transformation, converting async middleware into a generator-based executor. This ensures the Vue injection context is preserved after each await.

// Works seamlessly with injection available after every await
export default defineMiddleware(async (to) => {
  await validateSession()
  const queryClient = useQueryClient() // Available now
  await queryClient.prefetchQuery(userQueryOptions)
})

Quick Start Guide

  1. Install the Plugin

    npm install -D vite-plugin-vue-middleware
    
  2. Configure Vite

    // vite.config.ts
    import vueMiddleware from 'vite-plugin-vue-middleware'
    
    export default defineConfig({
      plugins: [vue(), vueMiddleware()],
    })
    
  3. Setup Middleware in Router

    // src/router/index.ts
    import { setupMiddleware } from 'virtual:vue-middleware'
    setupMiddleware(router)
    
  4. Create Middleware Files

    // src/middleware/01.auth.global.ts
    import { defineMiddleware } from 'virtual:vue-middleware'
    
    export default defineMiddleware(async (to) => {
      const isLoggedIn = await checkAuth()
      if (!isLoggedIn && to.path !== '/login') return '/login'
    })
    

File Naming Conventions

  • auth.global.ts: Executes on every route navigation
  • 01.auth.global.ts: Global middleware with execution order controlled by a numeric prefix
  • admin.ts: Executes only when meta.middleware includes 'admin'

Compatibility with unplugin-vue-router

For those using file-based routing with unplugin-vue-router, middleware can be declared directly within .vue files:

<script setup lang="ts">
definePage({
  meta: {
    middleware: ['auth', 'admin'],
  },
})
</script>

This approach includes full type safety.

Get Involved

Explore the project on GitHub or npm. Feedback and contributions are welcome, particularly if you've encountered the async context issue.

A star on GitHub would greatly help in bringing this project to a wider audience.

Discussion

0 Comments

Leave a Comment

Comments are moderated and will appear after approval.