DEV Community

Pramod Boda
Pramod Boda

Posted on Edited on

Recommended Industry-Standard React + TypeScript Folder Structure

Same architecture as before, adapted for TypeScript conventions — .tsx/.ts extensions, a dedicated types/ system, strict config files, and typed everything.

my-app/
├── public/
│   ├── favicon.ico
│   ├── robots.txt
│   └── index.html
│
├── src/
│   ├── assets/
│   │   ├── images/
│   │   ├── fonts/
│   │   └── icons/
│   │
│   ├── components/
│   │   ├── common/
│   │   │   ├── Button/
│   │   │   │   ├── Button.tsx
│   │   │   │   ├── Button.module.css
│   │   │   │   ├── Button.types.ts
│   │   │   │   ├── Button.test.tsx
│   │   │   │   └── index.ts
│   │   │   └── Modal/
│   │   └── layout/
│   │       ├── Header/
│   │       └── Footer/
│   │
│   ├── features/                     # Feature-based modules
│   │   ├── auth/
│   │   │   ├── components/
│   │   │   │   └── LoginForm.tsx
│   │   │   ├── hooks/
│   │   │   │   └── useAuth.ts
│   │   │   ├── services/
│   │   │   │   └── authService.ts
│   │   │   ├── types/
│   │   │   │   └── auth.types.ts
│   │   │   ├── authSlice.ts          # Redux Toolkit slice / Zustand store
│   │   │   └── index.ts
│   │   ├── dashboard/
│   │   └── profile/
│   │
│   ├── pages/
│   │   ├── Home/
│   │   │   ├── Home.tsx
│   │   │   └── Home.module.css
│   │   ├── About/
│   │   └── NotFound/
│   │
│   ├── routes/
│   │   ├── AppRoutes.tsx
│   │   ├── ProtectedRoute.tsx
│   │   └── routes.types.ts
│   │
│   ├── hooks/                        # Global/shared custom hooks
│   │   ├── useDebounce.ts
│   │   ├── useFetch.ts
│   │   └── useLocalStorage.ts
│   │
│   ├── context/
│   │   ├── ThemeContext.tsx
│   │   └── AuthContext.tsx
│   │
│   ├── store/                        # Global state management
│   │   ├── index.ts
│   │   ├── hooks.ts                  # Typed useDispatch/useSelector
│   │   └── slices/
│   │       └── userSlice.ts
│   │
│   ├── services/                     # API layer
│   │   ├── api.ts                    # Axios instance/config (typed)
│   │   ├── authService.ts
│   │   └── userService.ts
│   │
│   ├── utils/
│   │   ├── formatDate.ts
│   │   ├── validators.ts
│   │   └── constants.ts
│   │
│   ├── types/                        # Global/shared TypeScript types
│   │   ├── index.ts                  # Re-exports
│   │   ├── user.types.ts
│   │   ├── api.types.ts
│   │   ├── env.d.ts                  # Typed process.env / import.meta.env
│   │   └── global.d.ts               # Ambient/global declarations
│   │
│   ├── styles/
│   │   ├── globals.css
│   │   ├── variables.css
│   │   └── theme.ts                  # Typed theme object (MUI/styled-components)
│   │
│   ├── config/
│   │   ├── env.ts                    # Typed env config
│   │   └── appConfig.ts
│   │
│   ├── App.tsx
│   ├── App.css
│   ├── main.tsx                      # Entry point (Vite) / index.tsx (CRA)
│   └── vite-env.d.ts                 # Vite's built-in type reference
│
├── tests/
│
├── .env
├── .env.example
├── .eslintrc.cjs
├── .prettierrc
├── .gitignore
├── tsconfig.json                     # TS compiler config
├── tsconfig.node.json                # For vite.config.ts itself
├── package.json
├── vite.config.ts
└── README.md
Enter fullscreen mode Exit fullscreen mode

What Changes vs. the JS Version

1. Every file gets typed extensions
.jsx → .tsx (files with JSX), .js → .ts (pure logic files like utils, services, hooks without JSX).

2. Dedicated types/ folders, at two levels
Global types (src/types/) — shared across the app: API response shapes, User model, env typings.
Feature-local types (features/auth/types/) — scoped to that domain only.

3. tsconfig.json is critical

{
  "compilerOptions": {
    "target": "ES2020",
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "skipLibCheck": true,
    "moduleResolution": "bundler",
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,
    "jsx": "react-jsx",
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  },
  "include": ["src"]
}
Enter fullscreen mode Exit fullscreen mode

4. Typed component pattern

// Button.types.ts
export interface ButtonProps {
  label: string;
  variant?: 'primary' | 'secondary';
  onClick?: () => void;
  disabled?: boolean;
}

// Button.tsx
import { FC } from 'react';
import { ButtonProps } from './Button.types';

const Button: FC<ButtonProps> = ({ label, variant = 'primary', onClick, disabled }) => {
  return (
    <button className={`btn btn-${variant}`} onClick={onClick} disabled={disabled}>
      {label}
    </button>
  );
};

export default Button;
Enter fullscreen mode Exit fullscreen mode

5. Typed API service layer

// services/api.ts
import axios, { AxiosInstance } from 'axios';

const api: AxiosInstance = axios.create({
  baseURL: import.meta.env.VITE_API_URL,
  timeout: 10000,
});

export default api;

// services/userService.ts
import api from './api';
import { User } from '@/types/user.types';

export const getUser = async (id: string): Promise<User> => {
  const { data } = await api.get<User>(`/users/${id}`);
  return data;
};
Enter fullscreen mode Exit fullscreen mode

6. Typed Redux store (if using Redux Toolkit)

// store/index.ts
import { configureStore } from '@reduxjs/toolkit';
import authReducer from '@/features/auth/authSlice';

export const store = configureStore({
  reducer: { auth: authReducer },
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

// store/hooks.ts — typed versions of useDispatch/useSelector
import { useDispatch, useSelector, TypedUseSelectorHook } from 'react-redux';
import type { RootState, AppDispatch } from './index';

export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
Enter fullscreen mode Exit fullscreen mode

Essential Packages for This Setup

npm install -D typescript @types/react @types/react-dom @types/node
npm install -D @typescript-eslint/parser @typescript-eslint/eslint-plugin
Enter fullscreen mode Exit fullscreen mode

Top comments (0)