@davaux/user-agent

Parses the incoming User-Agent header and exposes structured device and browser information through ctx.state.userAgent. Useful for serving different content to mobile vs. desktop clients, blocking bots, or analytics.

Installation

npm install @davaux/user-agent

Setup

// davaux.config.ts
import { defineConfig } from 'davaux/config'
import { userAgent } from '@davaux/user-agent'

export default defineConfig({
  middleware: [userAgent()],
})

Available fields

After the middleware runs, ctx.state.userAgent exposes:

FieldTypeDescription
rawstringThe original User-Agent string
isMobilebooleantrue if the client is a mobile device (excludes tablets)
isTabletbooleantrue if the client is a tablet (iPad, Android without Mobile token)
isDesktopbooleantrue if the client is a desktop/laptop (not bot, mobile, or tablet)
isBotbooleantrue if the client appears to be a crawler/bot
botNamestring | undefinedBot name when isBot is true, e.g. 'Googlebot', 'curl'
deviceType'mobile' | 'tablet' | 'desktop' | 'bot' | 'unknown'Normalized device category
browserNamestring | undefinede.g. 'Chrome', 'Firefox', 'Safari'
browserVersionstring | undefinede.g. '124.0.0'
osNamestring | undefinede.g. 'Windows', 'macOS', 'iOS', 'Android'
osVersionstring | undefinede.g. '14.6'
enginestring | undefinedRendering engine: 'Blink', 'Gecko', 'WebKit'
partialbooleantrue when signals are conflicting or insufficient — e.g. a non-browser runtime on a mobile/tablet OS, or a Mozilla UA with no detectable browser or OS

Usage examples

Redirect mobile users

// src/middleware.ts
import { defineMiddleware, redirect } from 'davaux'

export default defineMiddleware(async (ctx, next) => {
  const { isMobile } = ctx.state.userAgent

  if (isMobile && !ctx.url.hostname.startsWith('m.')) {
    redirect(`https://m.example.com${ctx.url.pathname}`)
  }

  return next()
})

Block bots from specific routes

// src/routes/api/_middleware.ts
import { defineMiddleware } from 'davaux'

export default defineMiddleware(async (ctx, next) => {
  if (ctx.state.userAgent.isBot) {
    return new Response('Forbidden', { status: 403 })
  }
  return next()
})

Serve different layouts based on device

// src/routes/_layout.tsx
import { defineLayout } from 'davaux'
import type { LayoutProps } from 'davaux'

export default defineLayout(({ children, ctx }: LayoutProps) => {
  const { isMobile } = ctx.state.userAgent

  return (
    <html>
      <body class={isMobile ? 'mobile-layout' : 'desktop-layout'}>
        {children}
      </body>
    </html>
  )
})

Analytics middleware

// src/middleware.ts
import { defineMiddleware } from 'davaux'

export default defineMiddleware(async (ctx, next) => {
  const ua = ctx.state.userAgent
  const result = await next()

  // Log structured analytics data after the response
  if (!ua.isBot) {
    analytics.track({
      path: ctx.url.pathname,
      device: ua.deviceType,
      browser: ua.browserName,
      os: ua.osName,
    })
  }

  return result
})

TypeScript

The package ships its own module augmentation, so ctx.state.userAgent is fully typed as soon as you import @davaux/user-agent anywhere in your project. No extra declarations needed.

If you need the type directly (e.g. for a helper function), import it from the package:

import type { UserAgent } from '@davaux/user-agent'

function logDevice(ua: UserAgent) {
  console.log(ua.deviceType, ua.browserName)
}

Notes

  • Parsing happens once per request and is cached in ctx.state
  • Bot detection uses a regularly maintained list of known bot user-agent patterns
  • For highly accurate bot detection or advanced device fingerprinting, consider a dedicated service alongside this middleware