Configuring Vite with ESLint and Prettier for React Projects

Table of Contents
Setting up a modern React project requires more than just scaffolding a boilerplate; it demands a robust, enforceable foundation for code quality and consistency. Vite has cemented its place as the premier build tool for modern web applications, offering unparallelled development speeds and an intuitive configuration system. However, integrating it with the evolving landscape of code quality tools—specifically, ESLint's new Flat Config system and strict TypeScript compilation—can present challenges even for seasoned developers.
In this comprehensive guide, we'll architect a highly strict, modern linting and formatting pipeline using Vite, ESLint's Flat Config (eslint.config.js), Prettier, and TypeScript. This approach abandons the legacy .eslintrc formats in favor of the unified and modular Flat Config, ensuring your configuration is future-proof and performant.
The Paradigm Shift: ESLint Flat Config
Historically, ESLint configurations relied on a cascading resolution system using .eslintrc files, which often led to confusion when multiple configuration files applied to the same directory. The Flat Config system entirely overhauls this by utilizing a single eslint.config.js file (or .mjs/.cjs) at the root of your project. This system evaluates an array of configuration objects sequentially, providing a deterministic and programmatic approach to defining rules.
The Flat Config system eliminates the extends and plugins resolution complexities, replacing them with standard JavaScript imports. This paradigm shift means we construct our linting rules as composing standard JavaScript objects, which provides type safety and clearer lineage of rule definitions.
Project Initialization
First, let's bootstrap a new Vite project with the React and TypeScript template.
npm create vite@latest modern-react-app -- --template react-ts
cd modern-react-app
npm install
With the foundation laid, we must install the necessary dependencies for our linting and formatting pipeline. Because we are targeting the modern Flat Config, we need the latest versions of ESLint and the associated TypeScript and React plugins.
npm install -D eslint prettier eslint-config-prettier eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-react-refresh @eslint/js typescript-eslint globals
Architecting the ESLint Flat Config
Create an eslint.config.js file at the root of your project. This file will export an array of configuration objects. We will leverage typescript-eslint to bridge the gap between ESLint and TypeScript's AST, allowing us to enforce type-aware linting rules.
import js from '@eslint/js';
import globals from 'globals';
import reactPlugin from 'eslint-plugin-react';
import reactHooksPlugin from 'eslint-plugin-react-hooks';
import reactRefreshPlugin from 'eslint-plugin-react-refresh';
import tseslint from 'typescript-eslint';
import prettierConfig from 'eslint-config-prettier';
export default tseslint.config(
// Base Ignore Configuration
{ ignores: ['dist', 'node_modules', 'coverage', 'vite.config.ts'] },
// Base JavaScript rules
js.configs.recommended,
// TypeScript Configuration (Strict + Type Checked)
...tseslint.configs.strictTypeChecked,
...tseslint.configs.stylisticTypeChecked,
{
languageOptions: {
parserOptions: {
project: ['./tsconfig.app.json', './tsconfig.node.json'],
tsconfigRootDir: import.meta.dirname,
},
},
},
// React and Environment Configuration
{
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2023,
sourceType: 'module',
globals: {
...globals.browser,
...globals.es2021,
},
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
},
plugins: {
react: reactPlugin,
'react-hooks': reactHooksPlugin,
'react-refresh': reactRefreshPlugin,
},
settings: {
react: {
version: 'detect',
},
},
rules: {
...reactPlugin.configs.recommended.rules,
...reactPlugin.configs['jsx-runtime'].rules,
...reactHooksPlugin.configs.recommended.rules,
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
// Strict React Rules
'react/prop-types': 'off', // Rely on TypeScript instead
'react/no-array-index-key': 'error', // Prevent subtle rendering bugs
'react/jsx-pascal-case': 'error',
'react/jsx-no-useless-fragment': 'warn',
},
},
// Prettier Integration (Must be last to override formatting rules)
prettierConfig
);
Analyzing the Configuration
- Type-Aware Linting: By utilizing
tseslint.configs.strictTypeChecked, we enable rules that utilize TypeScript's type checker. This requires specifying theprojectproperty inparserOptions, pointing to our TypeScript configurations. This setup catches deep architectural flaws, such as unsafe any types, floating promises, and incorrect template literal usages. - React 17+ JSX Transform: The inclusion of
reactPlugin.configs['jsx-runtime'].rulesensures ESLint understands the modern JSX transform, preventing false positives for missing React imports. - React Refresh: Vite's Fast Refresh mechanism relies on predictable component exports. The
react-refresh/only-export-componentsrule enforces that modules containing components only export components, ensuring Fast Refresh operates reliably. - Prettier Integration: The
eslint-config-prettierplugin is appended at the very end of the array. The Flat Config system evaluates objects sequentially, meaning the last object takes precedence. This effectively disables any ESLint rules that might conflict with Prettier's formatting opinions.
Configuring Prettier
Prettier requires minimal configuration, but standardizing it ensures consistency across development environments. Create a .prettierrc file in the root directory:
{
"semi": true,
"trailingComma": "all",
"singleQuote": true,
"printWidth": 90,
"tabWidth": 2,
"endOfLine": "lf",
"jsxSingleQuote": false,
"arrowParens": "always"
}
Additionally, define a .prettierignore file to exclude generated assets and configuration files:
dist
coverage
node_modules
*.html
vite.config.ts
Enforcing Strict TypeScript
To maximize the benefits of type-aware linting, your TypeScript configuration must be equally rigorous. Modify your tsconfig.app.json (as standardized by recent Vite templates) to enforce strict type checking:
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Strict Type-Checking Options */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUncheckedIndexedAccess": true, // Highly recommended for array access safety
"exactOptionalPropertyTypes": true
},
"include": ["src"]
}
The addition of noUncheckedIndexedAccess is critical. It forces developers to handle undefined cases when accessing array or record elements by index, bridging a common gap between TypeScript's static analysis and runtime reality.
Integrating with Vite
While IDE integration handles inline linting, integrating ESLint directly into the Vite build process ensures that CI/CD pipelines and production builds fail if linting errors persist.
To achieve this, we can utilize vite-plugin-eslint. However, with the migration to Flat Config, you must ensure the plugin or a custom rollup plugin supports ESLint 9+ and the Flat Config API. Alternatively, executing linting as a pre-build step is a highly robust approach.
Update your package.json scripts:
{
"scripts": {
"dev": "vite",
"build": "npm run typecheck && npm run lint && vite build",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier --write \"src/**/*.{ts,tsx,css,md}\"",
"typecheck": "tsc --noEmit -p tsconfig.app.json && tsc --noEmit -p tsconfig.node.json",
"preview": "vite preview"
}
}
This sequence guarantees that npm run build will perform type checking and linting before attempting to bundle the application. If any strict type rules or linting policies are violated, the build aborts.
Conclusion
Migrating to ESLint's Flat Config alongside Vite and Prettier establishes a definitive standard for modern React development. By leaning heavily into type-aware linting through typescript-eslint and enforcing strict TypeScript compiler options, teams can proactively eliminate entire classes of runtime errors. This configuration provides a deterministic, highly performant, and future-proof foundation, allowing developers to focus on architectural complexity rather than debating code style or chasing subtle typing bugs.
You Might Also Like
Free In-Browser Developer Tools
Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.
Related Articles

React Query vs SWR: Choosing the Right Data Fetching Library in 2026
Comprehensive comparison of TanStack Query (React Query) and SWR: caching strategies, optimistic updates, mutation lifecycle, and React 19 compatibility.
Read more
Building a Type-Safe useLocalStorage Hook in React
A deep dive into creating a modern, robust, and type-safe useLocalStorage hook in React, addressing Next.js hydration mismatches and leveraging advanced TypeScript generics.
Read more
Custom React Hook performance Optimization Patterns
Master custom React hook performance optimization using stable ref caching, listener batching, memoized selectors, and profiler techniques.
Read more