This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- @vitejs/plugin-react uses Babel for Fast Refresh
- @vitejs/plugin-react-swc uses SWC for Fast Refresh
This project now requires:
- Node.js >= 20.19.0 (recommended: latest 22.x LTS) because Vite 7 uses the modern
crypto.hash
API and enforces a minimum Node version. Older Node (e.g. 18.x) will fail with errors likecrypto.hash is not a function
and a startup warning. - npm 10+ (bundled with Node 22) or a compatible package manager.
node -v # should be >= v20.19.0 (prefer v22.x)
npm -v
If you use multiple version managers (e.g. both asdf
and nvm
), ensure the active Node comes from one consistent tool. A leftover shim (such as ~/.asdf/shims/node
pointing to an older version) can cause npm run dev
to appear to use the wrong Node even if node -v
shows the correct version. Remove or reorder paths so the desired Node (from nvm
) comes first.
If you see the warning requesting an upgrade or the crypto.hash
error:
- Install a newer Node:
nvm install 22 && nvm use 22
. - Reinstall dependencies:
rm -rf node_modules && npm install
. - Clear Vite cache if present:
rm -rf node_modules/.vite
. - Start dev server:
npm run dev
.
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
export default tseslint.config([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
// Other configs...
// Remove tseslint.configs.recommended and replace with this
...tseslint.configs.recommendedTypeChecked,
// Alternatively, use this for stricter rules
...tseslint.configs.strictTypeChecked,
// Optionally, add this for stylistic rules
...tseslint.configs.stylisticTypeChecked,
// Other configs...
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
]);
You can also install eslint-plugin-react-x and eslint-plugin-react-dom for React-specific lint rules:
// eslint.config.js
import reactX from 'eslint-plugin-react-x';
import reactDom from 'eslint-plugin-react-dom';
export default tseslint.config([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
// Other configs...
// Enable lint rules for React
reactX.configs['recommended-typescript'],
// Enable lint rules for React DOM
reactDom.configs.recommended,
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
]);
All React components are declared as const arrow functions and exported using named exports (no default exports). Example:
// Good
export const MyWidget: React.FC<Props> = ({ title }) => {
/* ... */
};
All shared interfaces / types live in src/types
(with subfolders like components
, context
, pages
, etc.) and are re-exported via the barrel file src/types/index.ts
for convenience:
import type { Character, Player } from '@/types';
Do NOT declare component/page/context types inline unless they are strictly local. Promote reusability and consistency.
Import order is enforced by eslint-plugin-simple-import-sort
with the following grouping (blank line between groups):
- External packages (React first) - e.g.
react
,react-dom
, other npm deps. - Explicit Type imports using
import type ...
(prefixed internally by the plugin astype:
) — keeps type-only imports isolated. - Internal project types (
src/types
barrel or any path containing/types
). - UI / feature modules (components, pages) in this order: atoms, molecules, organisms, templates, pages (also any remaining
components/
path). - Other internal relative modules (hooks, context, services, utils) excluding styles.
- CSS / style files (always last).
Example:
import React from 'react';
import { createRoot } from 'react-dom/client';
import type { Character } from '@/types';
import { CharacterCard } from '@/components/molecules/CharacterCard/CharacterCard';
import { BattlePage } from '@/pages/BattlePage/BattlePage';
import { useGame } from '@/context/GameContext';
import { getCharacters } from '@/services/characters';
import './BattlePage.module.css';
The linter will auto-fix ordering; do not fight it—run npm run lint:fix
or rely on the pre-commit hook.
husky
+ lint-staged
run automatically on staged files:
- ESLint (with
--fix
) on*.ts, *.tsx
- Prettier formatting for TypeScript, JavaScript, JSON, CSS, Markdown
If a file fails lint (e.g. unused variable), the commit is aborted. Fix and re-stage.
npm run lint # strict (no warnings allowed)
npm run lint:fix # auto-fix where possible
npm run format # prettier across repo
E2E tests are configured to run automatically on pre-push to ensure the full application works before sharing changes.
npm run test:e2e # run all E2E tests
npm run test:e2e:ui # run tests with Playwright UI
npm run test:e2e:debug # run with debugger
npm run test:e2e:report # view test reports
- Pre-commit: Runs linting, type checking, unit tests, and build
- Pre-push: Runs E2E tests (Playwright) to ensure full app functionality
- Tests located in
e2e/
directory - Page Object Model pattern for maintainable tests
- Stable
data-testid
selectors for reliable element targeting - Cross-browser testing (Chromium, Firefox, WebKit)
- Login tests: Fully functional with fallback strategies
- Dashboard tests: Skip gracefully when Auth0 not configured
- Test environment setup documented in
E2E_TEST_SUMMARY.md
- Create or extend a file under
src/types/...
. - Export from that file and add to
src/types/index.ts
(barrel) if broadly reused. - Use
import type { X } from '@/types';
.
- Named exports improve refactor safety and tooling.
- Centralized types reduce duplication and drift.
- Deterministic import order improves diff clarity and avoids merge noise.
If imports reorder unexpectedly, let ESLint auto-fix instead of manually adjusting. If a new path category emerges, adjust the grouping in eslint.config.js
.
Potential additions:
- Path aliases (
@/components
, etc.) already partially supported viavite-tsconfig-paths
; ensure tsconfig path mappings if expanded. - Add stricter
typescript-eslint
type checking config. - Enforce exhaustive deps for custom hooks beyond react-hooks defaults.
Environment variables (Supabase, Auth, etc.) go in a local .env
(and optional .env.local
). These files are git-ignored. Do NOT commit secrets. If you need to document required keys, create a .env.example
without values or with placeholder values. Never place real credentials in the repo or commit history.
For environment requirements and general framework notes, see earlier sections in this README.
We integrated Storybook to document and iterate on UI components.
- Run Storybook:
npm run storybook
- Build static Storybook:
npm run build-storybook
- Stories live alongside components as
*.stories.tsx
- Path alias
@/*
is supported in stories via Vite tsconfig paths. - Linting: Story files allow default export for Storybook meta only.
- Addons: a11y, docs, onboarding. Basic light/dark backgrounds configured in
.storybook/preview.ts
.
Conventions when adding components:
- No
export default
in components; only named exports. - Reusable prop types under
src/types
and exported viasrc/types/index.ts
. - Follow import grouping (see
AGENTS.md
).