DEV Community

Pramod Boda
Pramod Boda

Posted on

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)