Skip to content

Middleware

Kimesh provides a powerful middleware system for route guards, authentication, analytics, and more. Middleware runs before route navigation completes.

Overview

Middleware can:

  • Block navigation and redirect users
  • Add data to navigation context
  • Run analytics or logging
  • Validate permissions

Defining Middleware

File-Based Middleware

Create middleware files in src/middleware/:

src/middleware/
├── auth.global.ts      # Runs on every navigation
├── admin.ts            # Named middleware (lazy-loaded)
└── 01.analytics.global.ts  # Priority-ordered global

Global Middleware (.global.ts suffix):

ts
// src/middleware/auth.global.ts
import { defineKimeshMiddleware } from '@kimesh/router-runtime'

export default defineKimeshMiddleware((to, from, context) => {
  const { user } = context.app

  // Allow public routes
  if (to.meta.public) return

  // Redirect to login if not authenticated
  if (!user.isLoggedIn) {
    return { path: '/login', query: { redirect: to.fullPath } }
  }
})

Named Middleware (loaded on-demand):

ts
// src/middleware/admin.ts
import { defineKimeshMiddleware } from '@kimesh/router-runtime'

export default defineKimeshMiddleware((to, from, context) => {
  const { user } = context.app

  if (!user.roles.includes('admin')) {
    return { path: '/unauthorized' }
  }
})

Middleware in Routes

Apply middleware to specific routes:

ts
// routes/admin/index.vue
import { createFileRoute } from '@kimesh/router-runtime'

export const Route = createFileRoute('/admin')({
  // Single named middleware
  middleware: 'admin',

  // Multiple middleware
  middleware: ['auth', 'admin'],

  // Inline middleware
  middleware: (to, from, context) => {
    if (!context.app.user.isAdmin) {
      return { path: '/' }
    }
  },

  // Mixed
  middleware: [
    'auth',
    (to, from, ctx) => {
      console.log('Navigating to admin')
    },
  ],
})

Middleware Context

ts
interface MiddlewareContext {
  to: RouteLocationNormalized // Destination route
  from: RouteLocationNormalized // Source route
  router: Router // Vue Router instance
  app: KimeshAppContext // Kimesh app context
  data: Record<string, unknown> // Shared data between middleware
}

Access your custom context:

ts
export default defineKimeshMiddleware((to, from, context) => {
  // Access queryClient
  const { queryClient } = context.app

  // Access custom injections from plugins
  const { myService } = context.app
})

Return Values

Middleware can return different values:

ts
// Allow navigation (implicit)
return
return undefined

// Block navigation
return false

// Redirect to path
return { path: '/login' }

// Redirect with options
return {
  path: '/login',
  query: { redirect: to.fullPath },
  replace: true,
}

// Redirect by name
return { name: 'login', params: { ... } }

Priority and Ordering

Global Middleware Order

Use numeric prefixes to control execution order:

src/middleware/
├── 01.setup.global.ts      # Runs first (priority 1)
├── 02.auth.global.ts       # Runs second (priority 2)
├── 10.analytics.global.ts  # Runs later (priority 10)
└── logger.global.ts        # No priority = alphabetical

Lower priority numbers run first.

Execution Flow

  1. Global middleware (by priority, then alphabetical)
  2. Route middleware (in array order)
  3. beforeLoad hook
  4. Route loader

Helpers

Redirect within middleware:

ts
import { defineKimeshMiddleware, navigateTo } from '@kimesh/router-runtime'

export default defineKimeshMiddleware((to, from, context) => {
  if (needsRedirect) {
    return navigateTo('/new-path', { replace: true })
  }
})

abortNavigation

Block navigation without redirect:

ts
import { defineKimeshMiddleware, abortNavigation } from '@kimesh/router-runtime'

export default defineKimeshMiddleware((to, from, context) => {
  if (shouldBlock) {
    abortNavigation()
    return false
  }
})

Composables

useNavigationMiddleware

Register middleware from components:

ts
import { useNavigationMiddleware } from '@kimesh/router-runtime'

// In component setup
useNavigationMiddleware((context) => {
  console.log(`Navigating from ${context.from.path} to ${context.to.path}`)
})

useNavigationGuard

Simpler guard registration:

ts
import { useNavigationGuard } from '@kimesh/router-runtime'

useNavigationGuard((context) => {
  if (hasUnsavedChanges.value) {
    return confirm('Discard changes?') ? undefined : false
  }
})

useAfterNavigation

Run code after navigation completes:

ts
import { useAfterNavigation } from '@kimesh/router-runtime'

useAfterNavigation((context) => {
  // Track page view
  analytics.track('page_view', { path: context.to.path })
})

Layer Middleware

Middleware from layers is merged with app middleware:

  • Same-name middleware: app version overrides layer version
  • Priority ordering applies across all sources
  • Layer middleware can be disabled by defining empty middleware with same name

Typed Middleware

For routes with params, use typed middleware:

ts
import type { TypedRouteMiddleware } from '@kimesh/router-runtime'

type PostParams = { postId: string }

export const postMiddleware: TypedRouteMiddleware<PostParams> = (to, from, ctx) => {
  // to.params.postId is typed as string
  console.log(`Viewing post: ${to.params.postId}`)
}

Generated Types

Kimesh generates middleware types in .kimesh/middleware.types.d.ts:

ts
export const middlewareNames = ['auth', 'admin', 'analytics'] as const
export type MiddlewareName = (typeof middlewareNames)[number]

Best Practices

  1. Use global middleware sparingly - Only for truly global concerns like auth
  2. Name middleware clearly - auth, admin, analytics not check1, mw2
  3. Keep middleware focused - One responsibility per middleware
  4. Use priority for dependencies - If middleware B depends on A, give A lower priority
  5. Colocate route-specific guards - Use inline middleware for route-specific logic

Released under the MIT License.