Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ You can use the following flags to detect the device type:

The user agent is also injected and accessible with `$device.userAgent`.

If a request has no `User-Agent` header, `$device.userAgent` is an empty string and the device is treated as a desktop with an unknown OS and browser.

The crawler detection is powered by the [crawler-user-agents](https://github.com/monperrus/crawler-user-agents) package.

## Usage
Expand Down Expand Up @@ -81,13 +83,11 @@ definePageMeta({
</script>
```

### Options

#### `defaultUserAgent`
## Static Generation

Sets the default value for the `user-agent` header (useful when running `npm run generate`).
Static generation (e.g. `nuxt generate`) is not supported. Prerendered pages are rendered once at build time without a user agent, so they are always rendered as a desktop.

Default: `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.39 Safari/537.36`
To detect the device on each request, deploy with `nuxt build` instead. For a client-side only app, set `ssr: false` to detect the device in the browser.

## Amazon CloudFront Support

Expand Down
3 changes: 0 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,6 @@
"test": "vitest run",
"test:watch": "vitest watch"
},
"dependencies": {
"defu": "^6.1.7"
},
"devDependencies": {
"@nuxt/devtools": "latest",
"@nuxt/eslint-config": "^1.17.0",
Expand Down
1 change: 0 additions & 1 deletion playground/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,4 @@ export default defineNuxtConfig({
compatibilityVersion: 5,
},
compatibilityDate: '2026-09-29',
device: {},
})
4 changes: 0 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 2 additions & 9 deletions src/module.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,16 @@
import { defu } from 'defu'
import { defineNuxtModule, addPlugin, addImportsDir, createResolver, addTemplate } from '@nuxt/kit'
import crawlers from 'crawler-user-agents' with { type: 'json' }
import { name, version } from '../package.json'
import type { ModuleOptions } from './types'

export default defineNuxtModule<ModuleOptions>({
export default defineNuxtModule({
meta: {
name,
configKey: 'device',
compatibility: {
nuxt: '>=4.0.0',
},
version,
},
setup(options, nuxt) {
nuxt.options.runtimeConfig.public.device = defu(nuxt.options.runtimeConfig.public.device, {
defaultUserAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.39 Safari/537.36',
})

setup() {
const { resolve } = createResolver(import.meta.url)

addPlugin(resolve('./runtime/plugin'))
Expand Down
14 changes: 3 additions & 11 deletions src/runtime/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,18 @@
import type { Plugin } from 'nuxt/app'
import type { Device } from './types'
import generateFlags from './generateFlags'
import { defineNuxtPlugin, reactive, useRequestHeaders, useRuntimeConfig } from '#imports'
import { defineNuxtPlugin, reactive, useRequestHeaders } from '#imports'

const plugin: Plugin<{ device: Device }> = defineNuxtPlugin(() => {
const runtimeConfig = useRuntimeConfig()

const defaultUserAgent = runtimeConfig.public.device.defaultUserAgent

let flags: Device

if (import.meta.server) {
const headers = useRequestHeaders()

const userAgent = headers['user-agent'] || defaultUserAgent

flags = reactive(generateFlags(userAgent, headers))
flags = reactive(generateFlags(headers['user-agent'] || '', headers))
}
else {
const userAgent = navigator.userAgent || defaultUserAgent

flags = reactive(generateFlags(userAgent))
flags = reactive(generateFlags(navigator.userAgent))
}

return {
Expand Down
13 changes: 0 additions & 13 deletions src/types.ts

This file was deleted.

10 changes: 10 additions & 0 deletions test/basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ describe('ssr', async () => {
expect(html).toContain('isCrawler')
})

it('treats an empty user agent as a desktop', async () => {
const html = await $fetch('/', {
headers: {
'User-Agent': '',
},
})

expect(parseHtml(html)).toEqual({ isDesktop: true, isMobile: false, isTablet: false, isMobileOrTablet: false, isDesktopOrTablet: true, isIos: false, isWindows: false, isMacOS: false, isLinux: false, isApple: false, isAndroid: false, isFirefox: false, isEdge: false, isChrome: false, isSafari: false, isSamsung: false, isCrawler: false })
})

describe('detects device', () => {
it('Samsung Galaxy S9', async () => {
const userAgent = 'Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36'
Expand Down
Loading