Harlan’s ESLint rules for Vue projects with focus on link hygiene, Nuxt best practices, and Vue reactivity patterns.
|
Made possible by my Sponsor Program 💖 Follow me @harlan_zw 🐦 |
Try the rules in action with a Nuxt ESLint interactive playground:
undefinedNote: These rules are experimental and may change. They will be submitted to the official Vue ESLint plugin for consideration.
The rules are organized into the following categories:
| Rule | Description |
|---|---|
link-ascii-only |
ensure link URLs contain only ASCII characters |
link-lowercase |
ensure link URLs do not contain uppercase characters |
link-no-double-slashes |
ensure link URLs do not contain consecutive slashes |
link-no-whitespace |
ensure link URLs do not contain whitespace characters |
nuxt-await-navigate-to |
enforce awaiting navigateTo() calls |
nuxt-no-redundant-import-meta |
disallow redundant import.meta.server or import.meta.client checks in scoped components |
nuxt-no-side-effects-in-async-data-handler |
disallow side effects in async data handlers |
nuxt-no-side-effects-in-setup |
disallow side effects in setup functions |
nuxt-prefer-navigate-to-over-router-push-replace |
prefer navigateTo() over router.push() or router.replace() |
nuxt-prefer-nuxt-link-over-router-link |
prefer NuxtLink over RouterLink |
vue-no-faux-composables |
stop fake composables that don’t use Vue reactivity |
vue-no-nested-reactivity |
don’t mix ref() and reactive() together |
vue-no-passing-refs-as-props |
don’t pass refs as props - unwrap them first |
vue-no-reactive-destructuring |
avoid destructuring reactive objects |
vue-no-ref-access-in-templates |
don’t use .value in Vue templates |
vue-no-torefs-on-props |
don’t use toRefs() on the props object |
Install the plugin:
pnpm add -D eslint-plugin-harlanzw
// eslint.config.js
import antfu from '@antfu/eslint-config'
import harlanzw from 'eslint-plugin-harlanzw'
export default antfu(
{
vue: true,
},
{
plugins: {
harlanzw
},
rules: {
'harlanzw/link-ascii-only': 'error',
'harlanzw/link-lowercase': 'error',
'harlanzw/link-no-double-slashes': 'error',
'harlanzw/link-no-whitespace': 'error',
'harlanzw/nuxt-await-navigate-to': 'error',
'harlanzw/nuxt-no-redundant-import-meta': 'error',
'harlanzw/nuxt-no-side-effects-in-async-data-handler': 'error',
'harlanzw/nuxt-no-side-effects-in-setup': 'error',
'harlanzw/nuxt-prefer-navigate-to-over-router-push-replace': 'error',
'harlanzw/nuxt-prefer-nuxt-link-over-router-link': 'error',
'harlanzw/vue-no-faux-composables': 'error',
'harlanzw/vue-no-nested-reactivity': 'error',
'harlanzw/vue-no-passing-refs-as-props': 'error',
'harlanzw/vue-no-reactive-destructuring': 'error',
'harlanzw/vue-no-ref-access-in-templates': 'error',
'harlanzw/vue-no-torefs-on-props': 'error'
}
}
)
// eslint.config.mjs
import harlanzw from 'eslint-plugin-harlanzw'
import withNuxt from './.nuxt/eslint.config.mjs'
export default withNuxt([{
plugins: {
harlanzw
},
rules: {
'harlanzw/link-ascii-only': 'error',
'harlanzw/link-lowercase': 'error',
'harlanzw/link-no-double-slashes': 'error',
'harlanzw/link-no-whitespace': 'error',
'harlanzw/nuxt-await-navigate-to': 'error',
'harlanzw/nuxt-no-redundant-import-meta': 'error',
'harlanzw/nuxt-no-side-effects-in-async-data-handler': 'error',
'harlanzw/nuxt-no-side-effects-in-setup': 'error',
'harlanzw/nuxt-prefer-navigate-to-over-router-push-replace': 'error',
'harlanzw/nuxt-prefer-nuxt-link-over-router-link': 'error',
'harlanzw/vue-no-faux-composables': 'error',
'harlanzw/vue-no-nested-reactivity': 'error',
'harlanzw/vue-no-passing-refs-as-props': 'error',
'harlanzw/vue-no-reactive-destructuring': 'error',
'harlanzw/vue-no-ref-access-in-templates': 'error',
'harlanzw/vue-no-torefs-on-props': 'error'
}
}])
Add the plugin to your ESLint configuration:
// eslint.config.js
import harlanzw from 'eslint-plugin-harlanzw'
import vueParser from 'vue-eslint-parser'
export default [
{
files: ['**/*.vue'],
languageOptions: {
parser: vueParser
},
plugins: {
harlanzw
},
rules: {
'harlanzw/link-ascii-only': 'error',
'harlanzw/link-lowercase': 'error',
'harlanzw/link-no-double-slashes': 'error',
'harlanzw/link-no-whitespace': 'error',
'harlanzw/nuxt-await-navigate-to': 'error',
'harlanzw/nuxt-no-redundant-import-meta': 'error',
'harlanzw/nuxt-no-side-effects-in-async-data-handler': 'error',
'harlanzw/nuxt-no-side-effects-in-setup': 'error',
'harlanzw/nuxt-prefer-navigate-to-over-router-push-replace': 'error',
'harlanzw/nuxt-prefer-nuxt-link-over-router-link': 'error',
'harlanzw/vue-no-faux-composables': 'error',
'harlanzw/vue-no-nested-reactivity': 'error',
'harlanzw/vue-no-passing-refs-as-props': 'error',
'harlanzw/vue-no-reactive-destructuring': 'error',
'harlanzw/vue-no-ref-access-in-templates': 'error',
'harlanzw/vue-no-torefs-on-props': 'error'
}
}
]
This plugin is based on eslint-plugin-antfu by Anthony Fu.
Licensed under the MIT license.