FE Vite (TypeScript)
Step-by-step setup for a React project using Vite (TypeScript) with ESLint v9 flat config, typescript-eslint v8, Prettier, Stylelint, Vitest, Husky v9, and Semantic Release.
1. Scaffold the project
1npm create vite@latest my-app -- --template react-ts
2cd my-app && npm install2. Install dev dependencies
1npm install -D \
2 vitest @vitest/coverage-v8 jsdom \
3 @testing-library/react @testing-library/jest-dom @testing-library/user-event \
4 @eslint/js globals typescript-eslint \
5 eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-react-refresh \
6 eslint-config-prettier eslint-plugin-prettier \
7 prettier \
8 stylelint stylelint-config-standard \
9 husky lint-staged \
10 @commitlint/config-conventional @commitlint/cli \
11 semantic-release @semantic-release/changelog @semantic-release/git \
12 @semantic-release/github @semantic-release/commit-analyzer \
13 @semantic-release/release-notes-generator \
14 @types/node3. Update vite.config.ts
1/// <reference types="vitest" />
2import { defineConfig } from 'vite';
3import react from '@vitejs/plugin-react';
4import { resolve } from 'path';
5
6export default defineConfig({
7 plugins: [react()],
8 resolve: {
9 alias: {
10 '@': resolve(__dirname, 'src'),
11 },
12 },
13 test: {
14 globals: true,
15 environment: 'jsdom',
16 setupFiles: ['./src/setupTests.ts'],
17 css: true,
18 },
19});4. Update tsconfig.json
1{
2 "compilerOptions": {
3 "baseUrl": ".",
4 "paths": {
5 "@/*": ["src/*"]
6 }
7 }
8}Note: Both vite.config.ts alias and tsconfig.json paths are required — Vite uses the alias for bundling, TypeScript uses paths for type checking.
5. Create eslint.config.js
1import js from '@eslint/js';
2import globals from 'globals';
3import tseslint from 'typescript-eslint';
4import reactHooks from 'eslint-plugin-react-hooks';
5import reactRefresh from 'eslint-plugin-react-refresh';
6import prettierConfig from 'eslint-config-prettier';
7import prettier from 'eslint-plugin-prettier';
8
9export default tseslint.config(
10 { ignores: ['dist'] },
11 {
12 extends: [js.configs.recommended, ...tseslint.configs.recommended],
13 files: ['**/*.{ts,tsx}'],
14 languageOptions: {
15 ecmaVersion: 'latest',
16 globals: { ...globals.browser },
17 },
18 plugins: {
19 'react-hooks': reactHooks,
20 'react-refresh': reactRefresh,
21 prettier,
22 },
23 rules: {
24 ...reactHooks.configs.recommended.rules,
25 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
26 '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
27 'prettier/prettier': 'error',
28 },
29 },
30 prettierConfig,
31);Note: typescript-eslint v8+ ships as a single unified package replacing the separate @typescript-eslint/parser and @typescript-eslint/eslint-plugin.
6. Create .prettierrc
1{
2 "singleQuote": true,
3 "trailingComma": "all",
4 "printWidth": 100,
5 "tabWidth": 2,
6 "semi": true
7}7. Create stylelint.config.mjs
1export default {
2 extends: ['stylelint-config-standard'],
3 rules: {
4 'at-rule-no-unknown': [
5 true,
6 {
7 ignoreAtRules: ['tailwind', 'apply', 'variants', 'responsive', 'screen'],
8 },
9 ],
10 'no-descending-specificity': null,
11 },
12};Note: stylelint-config-prettier is no longer needed — Stylelint v15+ removed all formatting rules by default, eliminating conflicts with Prettier.
8. Configure Husky and lint-staged
Add to package.json:
1{
2 "scripts": {
3 "prepare": "husky"
4 },
5 "lint-staged": {
6 "*.{ts,tsx}": ["eslint --fix", "prettier --write"],
7 "*.{css,scss}": ["stylelint --fix", "prettier --write"],
8 "*.{json,md,yml}": ["prettier --write"]
9 }
10}Initialize Husky and set up hooks:
1npx husky init
2echo "npx lint-staged" > .husky/pre-commit
3echo "npx --no -- commitlint --edit \"$1\"" > .husky/commit-msgNote: Husky v9 replaced husky install with husky init. The prepare script is now just "husky".
9. Create commitlint.config.cjs
1module.exports = {
2 extends: ['@commitlint/config-conventional'],
3};Note: Use .cjs extension — Vite projects have "type": "module", so CommonJS configs require explicit .cjs.
10. Create src/setupTests.ts
import '@testing-library/jest-dom';11. Create release.config.cjs
1module.exports = {
2 branches: ['main'],
3 plugins: [
4 '@semantic-release/commit-analyzer',
5 '@semantic-release/release-notes-generator',
6 [
7 '@semantic-release/changelog',
8 {
9 changelogFile: 'CHANGELOG.md',
10 changelogTitle: '# Changelog',
11 },
12 ],
13 [
14 '@semantic-release/git',
15 {
16 assets: ['CHANGELOG.md', 'package.json', 'package-lock.json'],
17 message: 'chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}',
18 },
19 ],
20 '@semantic-release/github',
21 ],
22};In CI/CD, make sure GITHUB_TOKEN is available in the environment.
12. Add npm scripts to package.json
1{
2 "scripts": {
3 "dev": "vite",
4 "build": "tsc -b && vite build",
5 "preview": "vite preview",
6 "lint": "eslint .",
7 "format": "prettier --write \"**/*.{ts,tsx,json,css,scss,md}\"",
8 "stylelint": "stylelint \"**/*.{css,scss}\" --cache",
9 "test": "vitest",
10 "test:ui": "vitest --ui",
11 "test:coverage": "vitest run --coverage",
12 "release": "semantic-release"
13 }
14}13. Optional: VSCode settings
1{
2 "editor.formatOnSave": true,
3 "editor.codeActionsOnSave": {
4 "source.fixAll.eslint": "explicit"
5 },
6 "eslint.validate": ["typescript", "typescriptreact"],
7 "prettier.enable": true
8}