npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

eslint-config-uta

v3.12.2

Published

Modular ESLint configuration for UTA (Universal Template Architecture) projects - architecture, imports, React best practices for web and mobile

Readme

eslint-config-uta

Enforce clean architecture in Next.js, React Native, and Expo projects - Stop architecture violations, circular dependencies, and framework anti-patterns before they ship.

npm version License: MIT

Supports: Next.js 13+ (App Router) • React Native • Expo • Remix


What Does It Do?

Catches these problems in your code:

// ❌ Architecture violation - feature importing another feature
import { UserCard } from '@/features/profile'  // in features/dashboard

// ❌ Circular dependency
import { validateUser } from '@/features/user'  // which imports from here

// ❌ Security risk - server code in client component
'use client'
import { db } from 'server-only'  // Leaks to client bundle!

// ❌ Performance issue - inline styles in React Native
<View style={{ padding: 16 }} />  // New object every render

// ❌ Bug - hooks in async Server Component
export default async function Page() {
  const [state, setState] = useState(0)  // Runtime error!
}

// ❌ Crash risk - Zod .parse() throws
const user = UserSchema.parse(data)  // Unhandled error crashes app

All auto-detected and reported by ESLint.


Quick Start

Install

pnpm add -D eslint-config-uta

Install Optional Plugins (Based on Your Stack)

# Next.js projects (recommended)
pnpm add -D @tanstack/eslint-plugin-query eslint-plugin-zod

# React Native projects
pnpm add -D eslint-plugin-react-native

Note: Most peer dependencies (TypeScript, React, Import plugins) are auto-installed by pnpm/npm 7+. Only optional ones need manual installation.

Configure

Next.js:

// eslint.config.mjs
import { dirname } from "path"
import { fileURLToPath } from "url"
import { FlatCompat } from "@eslint/eslintrc"
import { createNextJSRecommendedPreset } from "eslint-config-uta/presets/nextjs-recommended"

const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
const compat = new FlatCompat({ baseDirectory: __dirname })

export default [
  ...compat.extends("next/core-web-vitals", "next/typescript"),

  ...createNextJSRecommendedPreset({
    extendsNextJSConfig: true,  // Required when using FlatCompat
    includeTanStackQuery: true,
    includeZod: true,
    includeRSC: true,
  }),
]

React Native:

// eslint.config.mjs
import { createReactNativeRecommendedPreset } from 'eslint-config-uta/presets/react-native-recommended'

export default createReactNativeRecommendedPreset()

Run

pnpm lint
pnpm lint --fix  # Auto-fix imports

Core Features

1. Architecture Boundaries

Enforces the three-layer UI architecture:

app/          → Can import: features, ui, core
features/     → Can import: ui, core (NOT other features)
ui/business/  → Can import: ui/patterns, ui/foundation, core
ui/patterns/  → Can import: ui/foundation, core
ui/foundation → Can import: core only
core/domains/ → Can import: core/shared only
core/shared/  → No internal imports

Catches:

  • Upward imports (foundation importing from business)
  • Feature-to-feature coupling
  • Core importing from UI
  • Circular dependencies

Rules:

  • uta/enforce-layer-boundaries
  • uta/no-cross-feature-imports
  • uta/enforce-barrel-exports
  • uta/no-deep-relative-imports

2. Next.js Server Components

Prevents React Server Component bugs:

// ❌ Server imports in Client Component (security)
'use client'
import { db } from 'server-only'

// ✅ Fixed
// Don't import server code in client components

// ❌ Hooks in async components (runtime error)
export default async function Page() {
  const [state, setState] = useState(0)
}

// ✅ Fixed - add 'use client'
'use client'
export default function Page() {
  const [state, setState] = useState(0)
}

// ❌ Missing 'use client' directive
export default function Counter() {
  const [count, setCount] = useState(0)  // Hooks need 'use client'
}

// ✅ Fixed
'use client'
export default function Counter() {
  const [count, setCount] = useState(0)
}

Rules:

  • uta/nextjs-no-server-only-in-client (auto-fixable)
  • uta/nextjs-no-hooks-in-server-component (auto-fixable)
  • uta/nextjs-enforce-use-client (auto-fixable)
  • @next/next/no-async-client-component
  • @next/next/no-html-link-for-pages
  • @next/next/no-img-element

3. TanStack Query Best Practices

Prevents stale data and performance issues:

// ❌ Missing cache invalidation
const mutation = useMutation({
  mutationFn: updateUser,
  // Cache never updates!
})

// ✅ Invalidate queries on success
const mutation = useMutation({
  mutationFn: updateUser,
  onSuccess: () => {
    queryClient.invalidateQueries({ queryKey: ['users'] })
  },
})

// ❌ Missing query key dependencies
const { data } = useQuery({
  queryKey: ['user'],
  queryFn: () => fetchUser(userId)  // userId not in key!
})

// ✅ Exhaustive dependencies
const { data } = useQuery({
  queryKey: ['user', userId],
  queryFn: () => fetchUser(userId)
})

// ❌ Unstable QueryClient
function App() {
  const queryClient = new QueryClient()  // New every render!
  return <QueryClientProvider client={queryClient}>...</>
}

// ✅ Stable reference
const queryClient = new QueryClient()
function App() {
  return <QueryClientProvider client={queryClient}>...</>
}

Rules:

  • uta/tanstack-require-mutation-invalidation
  • @tanstack/query/exhaustive-deps
  • @tanstack/query/stable-query-client

4. Zod Validation Safety

Prevents runtime crashes:

// ❌ .parse() throws on invalid data
const user = UserSchema.parse(data)  // Crashes app!

// ✅ .safeParse() returns error object
const result = UserSchema.safeParse(data)
if (!result.success) {
  return { error: result.error.message }
}

// ❌ Schema in wrong location
// features/user/UserForm.tsx
const UserSchema = z.object({ ... })

// ✅ Schemas in core/domains
// core/domains/user/schemas/user.schema.ts
export const UserSchema = z.object({ ... })

// ❌ Verbose union syntax
const StatusSchema = z.union([
  z.literal('active'),
  z.literal('inactive')
])

// ✅ Concise enum
const StatusSchema = z.enum(['active', 'inactive'])

Rules:

  • uta/zod-prefer-safe-parse
  • uta/zod-schema-location
  • zod/prefer-enum
  • zod/require-strict (optional)

5. React Native Performance

Critical mobile performance rules:

// ❌ Inline styles (creates new object every render)
<ScrollView>
  {items.map(item => (
    <View style={{ padding: 16, margin: 8 }} />
  ))}
</ScrollView>

// ✅ StyleSheet.create (optimized)
const styles = StyleSheet.create({
  item: { padding: 16, margin: 8 }
})
<ScrollView>
  {items.map(item => <View style={styles.item} />)}
</ScrollView>

// ❌ .map() in ScrollView (no virtualization)
<ScrollView>
  {users.map(user => <UserCard user={user} />)}
</ScrollView>

// ✅ FlatList (virtualized)
<FlatList
  data={users}
  renderItem={({ item }) => <UserCard user={item} />}
  keyExtractor={item => item.id}
/>

// ❌ Hardcoded colors
const styles = StyleSheet.create({
  text: { color: '#FF0000' }
})

// ✅ Theme colors
import { colors } from '@/styles/theme'
const styles = StyleSheet.create({
  text: { color: colors.error }
})

// ❌ Raw text (runtime error)
<View>Hello</View>

// ✅ Text wrapper
<View><Text>Hello</Text></View>

Rules:

  • uta/rn-no-inline-styles-performance
  • uta/rn-prefer-flatlist-for-lists
  • react-native/no-inline-styles
  • react-native/no-color-literals
  • react-native/no-raw-text
  • react-native/no-unused-styles

6. Import Organization

Auto-sorts imports with framework awareness:

// ❌ Messy imports
import { Button } from '@/ui/foundation/button'
import { useState } from 'react'
import './styles.css'
import type { User } from './types'

// ✅ Auto-sorted (run: eslint --fix)
import { useState } from 'react'

import { Button } from '@/ui/foundation/button'

import type { User } from './types'
import './styles.css'

Rules:

  • simple-import-sort/imports (auto-fixable)
  • simple-import-sort/exports (auto-fixable)
  • import/no-cycle (circular dependency detection)
  • @typescript-eslint/consistent-type-imports

Configuration Options

Preset Functions

All presets accept these options:

createNextJSRecommendedPreset({
  // Severity level
  severity: 'error',  // 'error' | 'warn'

  // Tech stack features (requires plugins)
  includeTanStackQuery: true,  // Requires @tanstack/eslint-plugin-query
  includeZod: true,            // Requires eslint-plugin-zod
  includeRSC: true,            // Next.js Server Components rules

  // Plugin conflict prevention
  extendsNextJSConfig: true,   // Set true when using FlatCompat
})

Custom Directory Structure

If your project doesn't use @/ or src/:

Option 1: Config file (recommended)

// uta.config.cjs
module.exports = {
  architecture: {
    pathAlias: '~',
    layers: {
      core: 'lib/core',
      ui: 'lib/components',
    },
  },
}
// eslint.config.mjs
import { createArchitectureConfig } from 'eslint-config-uta/plugins/uta/utils/architecture-factory.js'

export default [
  ...createArchitectureConfig({}, { enableConfigFile: true }),
]

Option 2: Inline settings

import { createArchitectureConfig } from 'eslint-config-uta/plugins/uta/utils/architecture-factory.js'

export default [
  ...createArchitectureConfig({
    pathAlias: '~',
    layers: {
      core: 'lib/core',
      ui: 'lib/components',
    },
  }),
]

Supported config files:

  • uta.config.cjs
  • .utarc / .utarc.json
  • package.json (under "uta" key)

Available Presets

Recommended (Balanced)

import { createNextJSRecommendedPreset } from 'eslint-config-uta/presets/nextjs-recommended'
import { createReactNativeRecommendedPreset } from 'eslint-config-uta/presets/react-native-recommended'

Includes: Architecture + Framework rules + Tech stack (optional plugins)

Strict (More Rigorous)

import { createNextJSStrictPreset } from 'eslint-config-uta/presets/nextjs-strict'
import { createReactNativeStrictPreset } from 'eslint-config-uta/presets/react-native-strict'

Adds: Explicit return types, stricter TypeScript, strict Zod mode

Minimal (Architecture Only)

import { createMinimalPreset } from 'eslint-config-uta/presets/minimal'

Only architecture boundary rules. For gradual adoption.


Troubleshooting

Missing Peer Dependency

Error: Cannot find package '@tanstack/eslint-plugin-query'

Solution 1: Install the plugin

pnpm add -D @tanstack/eslint-plugin-query

Solution 2: Disable the feature

createNextJSRecommendedPreset({
  includeTanStackQuery: false,
})

Plugin Conflict Error

Error: Plugin "react" was conflicted

Cause: Using FlatCompat with Next.js config registers plugins twice.

Solution: Set extendsNextJSConfig: true

createNextJSRecommendedPreset({
  extendsNextJSConfig: true,  // Prevents duplicate plugin registration
})

Peer Dependencies Not Auto-Installing

pnpm users: Add to .npmrc:

auto-install-peers=true

Then reinstall:

rm -rf node_modules pnpm-lock.yaml
pnpm install

Note: Only required peers auto-install. Optional peers (TanStack Query, Zod, React Native) need manual installation.


ESLint 8 Legacy Config

Recommended: Upgrade to ESLint 9 flat config.

If you must use ESLint 8:

// .eslintrc.js
const { architectureConfig } = require('eslint-config-uta')
const utaPlugin = require('eslint-config-uta/plugin')

module.exports = {
  plugins: ['uta'],
  overrides: architectureConfig.map(config => ({
    files: config.files,
    rules: config.rules,
  })),
}

Gradual Adoption

Start with warnings, upgrade to errors when ready:

export default [
  ...createNextJSRecommendedPreset({
    severity: 'warn',  // Start here
  }),

  // Override specific rules
  {
    rules: {
      // Enforce these immediately
      'uta/nextjs-no-server-only-in-client': 'error',

      // Keep as warnings for now
      'uta/enforce-layer-boundaries': 'warn',
    },
  },
]

Migration steps:

  1. Install with severity: 'warn'
  2. Run eslint --fix to auto-fix imports
  3. Fix remaining warnings
  4. Upgrade to severity: 'error'

Performance

Optimized for large codebases:

  • Single-pass AST traversal per rule
  • Minimal file system operations
  • Efficient regex path matching
  • Framework detection: <5ms per project

Benchmark:

  • ~0.5-1ms overhead per file
  • Scales to 1000+ file projects
  • No noticeable impact on ESLint runtime

FAQ

Which preset should I use?

Next.js App Router: nextjs-recommended React Native/Expo: react-native-recommended Gradual adoption: minimal (architecture only)

Start with recommended. Upgrade to strict when your team is ready for stricter TypeScript rules.


Can I disable specific rules?

Yes. Override in your config:

export default [
  ...createNextJSRecommendedPreset(),

  {
    rules: {
      'uta/enforce-barrel-exports': 'off',
      'simple-import-sort/imports': 'warn',
    },
  },
]

Does this work with monorepos?

Yes. Each package can have its own eslint.config.mjs and uta.config.cjs.


What peer dependencies do I need?

Auto-installed (required):

  • TypeScript ESLint plugins
  • React plugins
  • Import plugins

Manual install (optional, based on features you enable):

  • @tanstack/eslint-plugin-query (for includeTanStackQuery: true)
  • eslint-plugin-zod (for includeZod: true)
  • eslint-plugin-react-native (for React Native projects)

Why so many peer dependencies?

Design choice: Each project chooses its plugin versions. This prevents:

  • Version conflicts
  • Duplicate plugin installations
  • Breaking changes forced on users

Modern package managers (pnpm, npm 7+) auto-install required peers. Only optional ones need manual installation.


Contributing

Contributions welcome! Please:

  1. Fork and create a feature branch
  2. Add tests in plugins/uta/tests/
  3. Run npm test
  4. Update CHANGELOG
  5. Submit PR

See CONTRIBUTING.md for detailed guidelines.


Links


License

MIT © UpdateTheApp


Made with ❤️ by the UpdateTheApp team